Add Nanoleaf support
This commit is contained in:
parent
7faa3b4875
commit
79b49873ec
19 changed files with 2950 additions and 32 deletions
|
|
@ -13,7 +13,7 @@
|
|||
- shared-windows
|
||||
- windows
|
||||
- windows-1809
|
||||
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
|
@ -37,7 +37,7 @@ before_script:
|
|||
script:
|
||||
- export $(dpkg-architecture)
|
||||
- ./scripts/build-appimage.sh
|
||||
|
||||
|
||||
artifacts:
|
||||
name: "${CI_PROJECT_NAME}_Linux_32_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
|
|
@ -56,7 +56,7 @@ before_script:
|
|||
script:
|
||||
- export $(dpkg-architecture)
|
||||
- ./scripts/build-appimage.sh
|
||||
|
||||
|
||||
artifacts:
|
||||
name: "${CI_PROJECT_NAME}_Linux_64_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
|
|
@ -183,8 +183,8 @@ before_script:
|
|||
image: fedora:36
|
||||
stage: build
|
||||
script:
|
||||
- dnf install rpmdevtools dnf-plugins-core -y
|
||||
- rpmdev-setuptree
|
||||
- dnf install rpmdevtools dnf-plugins-core libcurl-devel -y
|
||||
- rpmdev-setuptree
|
||||
- ls /root/
|
||||
- cp fedora/OpenRGB.spec /root/rpmbuild/SPECS
|
||||
- cp ../OpenRGB /root/rpmbuild/SOURCES/ -r
|
||||
|
|
@ -357,42 +357,42 @@ before_script:
|
|||
- '& cmd.exe /C "vcvarsall.bat x86 & set" | Foreach-Object { if ($_ -match "(.*?)=(.*)") { Set-Item -force -path "Env:\$($matches[1])" -value "$($matches[2])" } }'
|
||||
- Pop-Location
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'downloading precompiled versions of qtbase, qttools (for windeployqt) and jom (for a more parallel nmake)'
|
||||
- mkdir _qt
|
||||
- mkdir _qt_download
|
||||
- Push-Location _qt_download
|
||||
- curl.exe -LJ -o qt-base.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win32_msvc2019/5.15.0-0-202005150700qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86.7z'
|
||||
- curl.exe -LJ -o qt-base.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win32_msvc2019/5.15.0-0-202005150700qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86.7z'
|
||||
- curl.exe -LJ -o qt-tools.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win32_msvc2019/5.15.0-0-202005150700qttools-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86.7z'
|
||||
- curl.exe -LJ -o qt-jom.zip 'https://qt-mirror.dannhauer.de/official_releases/jom/jom.zip'
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'extracting the downloaded qt binaries'
|
||||
- 7z x qt-base.7z '-o../_qt' -y
|
||||
- 7z x qt-tools.7z '-o../_qt' -y
|
||||
- 7z x qt-jom.zip '-o../_qt' -y
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'turn the qt install from enterprise to foss; remove the licensing checks'
|
||||
- ${qconfig-pri-folder} = '..\_qt\5.15.0\msvc2019\mkspecs\qconfig.pri'
|
||||
- (Get-Content ${qconfig-pri-folder}).replace('QT_EDITION = Enterprise', 'QT_EDITION = OpenSource') | Set-Content ${qconfig-pri-folder}
|
||||
- (Get-Content ${qconfig-pri-folder}).replace('QT_LICHECK = licheck.exe', '') | Set-Content ${qconfig-pri-folder}
|
||||
- Pop-Location
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'run qmake and generate the msvc nmake makefile'
|
||||
- mkdir _build; cd _build
|
||||
- ..\_qt\5.15.0\msvc2019\bin\qmake ..\OpenRGB.pro
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'start the actual build with jom instead of nmake; for speed'
|
||||
- ..\_qt\jom
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'run windeployqt to automatically copy the needed dll files'
|
||||
- ..\_qt\5.15.0\msvc2019\bin\windeployqt --no-angle --no-translations --no-opengl-sw --no-system-d3d-compiler --no-compiler-runtime --no-webkit2 .\release\
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'Moving results for upload'
|
||||
- mv release ../'OpenRGB Windows 32-bit'
|
||||
- _fold_final_
|
||||
|
|
@ -426,42 +426,42 @@ before_script:
|
|||
- '& cmd.exe /C "vcvarsall.bat x64 & set" | Foreach-Object { if ($_ -match "(.*?)=(.*)") { Set-Item -force -path "Env:\$($matches[1])" -value "$($matches[2])" } }'
|
||||
- Pop-Location
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'downloading precompiled versions of qtbase, qttools (for windeployqt) and jom (for a more parallel nmake)'
|
||||
- mkdir _qt
|
||||
- mkdir _qt_download
|
||||
- Push-Location _qt_download
|
||||
- curl.exe -LJ -o qt-base.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win64_msvc2019_64/5.15.0-0-202005150700qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64.7z'
|
||||
- curl.exe -LJ -o qt-base.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win64_msvc2019_64/5.15.0-0-202005150700qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64.7z'
|
||||
- curl.exe -LJ -o qt-tools.7z 'https://qt-mirror.dannhauer.de/online/qtsdkrepository/windows_x86/desktop/qt5_5150/qt.qt5.5150.win64_msvc2019_64/5.15.0-0-202005150700qttools-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64.7z'
|
||||
- curl.exe -LJ -o qt-jom.zip 'https://qt-mirror.dannhauer.de/official_releases/jom/jom.zip'
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'extracting the downloaded qt binaries'
|
||||
- 7z x qt-base.7z '-o../_qt' -y
|
||||
- 7z x qt-tools.7z '-o../_qt' -y
|
||||
- 7z x qt-jom.zip '-o../_qt' -y
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'turn the qt install from enterprise to foss; remove the licensing checks'
|
||||
- ${qconfig-pri-folder} = '..\_qt\5.15.0\msvc2019_64\mkspecs\qconfig.pri'
|
||||
- (Get-Content ${qconfig-pri-folder}).replace('QT_EDITION = Enterprise', 'QT_EDITION = OpenSource') | Set-Content ${qconfig-pri-folder}
|
||||
- (Get-Content ${qconfig-pri-folder}).replace('QT_LICHECK = licheck.exe', '') | Set-Content ${qconfig-pri-folder}
|
||||
- Pop-Location
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'run qmake and generate the msvc nmake makefile'
|
||||
- mkdir _build; cd _build
|
||||
- ..\_qt\5.15.0\msvc2019_64\bin\qmake ..\OpenRGB.pro
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'start the actual build with jom instead of nmake; for speed'
|
||||
- ..\_qt\jom
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'run windeployqt to automatically copy the needed dll files'
|
||||
- ..\_qt\5.15.0\msvc2019_64\bin\windeployqt --no-angle --no-translations --no-opengl-sw --no-system-d3d-compiler --no-compiler-runtime --no-webkit2 .\release\
|
||||
- _fold_final_
|
||||
|
||||
|
||||
- _fold_start_ 'Moving results for upload'
|
||||
- mv release ../'OpenRGB Windows 64-bit'
|
||||
- _fold_final_
|
||||
|
|
|
|||
211
Controllers/NanoleafController/NanoleafController.cpp
Normal file
211
Controllers/NanoleafController/NanoleafController.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/*-----------------------------------------*\
|
||||
| NanoleafController.cpp |
|
||||
| |
|
||||
| API Interface for Nanoleaf devices |
|
||||
| |
|
||||
| Nikita Rushmanov 01/13/2022 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#include "NanoleafController.h"
|
||||
#include "LogManager.h"
|
||||
#include <curl/curl.h>
|
||||
|
||||
std::size_t WriteMemoryCallback(const char* in, std::size_t size, std::size_t num, std::string* out)
|
||||
{
|
||||
const std::size_t totalBytes(size * num);
|
||||
out->append(in, totalBytes);
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
long APIRequest(std::string method, std::string location, std::string URI, json* request_data = nullptr, json* response_data = nullptr)
|
||||
{
|
||||
const std::string url("http://"+location+URI);
|
||||
|
||||
CURL* curl = curl_easy_init();
|
||||
|
||||
// Set remote URL.
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
|
||||
// Don't bother trying IPv6, which would increase DNS resolution time.
|
||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
|
||||
// Don't wait forever, time out after 10 seconds.
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
// Follow HTTP redirects if necessary.
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
if(request_data)
|
||||
{
|
||||
// LOG_DEBUG("[Nanoleaf] Sending data: %s", request_data->dump().c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request_data->dump().c_str());
|
||||
}
|
||||
|
||||
// Response information.
|
||||
long httpCode(0);
|
||||
std::unique_ptr<std::string> httpData(new std::string());
|
||||
|
||||
// Hook up data handling function.
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Hook up data container (will be passed as the last |
|
||||
| parameter to the callback handling function). Can be any |
|
||||
| pointer type, since it will internally be passed as a |
|
||||
| void pointer. |
|
||||
\*---------------------------------------------------------*/
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());
|
||||
|
||||
// Run our HTTP GET command, capture the HTTP response code, and clean up.
|
||||
curl_easy_perform(curl);
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (httpCode/100 == 2)
|
||||
{
|
||||
if(response_data)
|
||||
{
|
||||
*response_data = json::parse(*httpData.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG("[Nanoleaf] HTTP %i:Could not %s from %s", httpCode, method, url);
|
||||
}
|
||||
|
||||
return httpCode;
|
||||
}
|
||||
|
||||
NanoleafController::NanoleafController(std::string a_address, int a_port, std::string a_auth_token)
|
||||
{
|
||||
address = a_address;
|
||||
port = a_port;
|
||||
auth_token = a_auth_token;
|
||||
location = address+":"+std::to_string(port);
|
||||
|
||||
json data;
|
||||
if(APIRequest("GET", location, "/api/v1/"+auth_token, nullptr, &data) == 200)
|
||||
{
|
||||
name = data["name"];
|
||||
serial = data["serialNo"];
|
||||
manufacturer = data["manufacturer"];
|
||||
firmware_version = data["firmwareVersion"];
|
||||
model = data["model"];
|
||||
|
||||
brightness = data["state"]["brightness"]["value"];
|
||||
selectedEffect = data["effects"]["select"];
|
||||
|
||||
for(json::const_iterator it = data["effects"]["effectsList"].begin(); it != data["effects"]["effectsList"].end(); ++it)
|
||||
{
|
||||
effects.push_back(it.value());
|
||||
}
|
||||
|
||||
for(json::const_iterator it = data["panelLayout"]["layout"]["positionData"].begin(); it != data["panelLayout"]["layout"]["positionData"].end(); ++it)
|
||||
{
|
||||
panel_ids.push_back(it.value()["panelId"].get<int>());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
std::string NanoleafController::Pair(std::string address, int port)
|
||||
{
|
||||
const std::string location = address+":"+std::to_string(port);
|
||||
|
||||
json data;
|
||||
if(APIRequest("POST", location, "/api/v1/new", nullptr, &data) == 200)
|
||||
{
|
||||
return data["auth_token"];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafController::Unpair(std::string address, int port, std::string auth_token)
|
||||
{
|
||||
const std::string location = address+":"+std::to_string(port);
|
||||
|
||||
// We really don't care if this fails.
|
||||
APIRequest("DELETE", location, "/api/v1/"+auth_token, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void NanoleafController::UpdateLEDs(std::vector<RGBColor>& colors)
|
||||
{
|
||||
// Requires StartExternalControl() to have been called prior.
|
||||
|
||||
if(model == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
uint8_t size = panel_ids.size();
|
||||
|
||||
uint8_t* message = (uint8_t*)malloc(size*7+6+1);
|
||||
|
||||
message[0] = (uint8_t)size;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
message[7*i+0+1] = (uint8_t)panel_ids[i];
|
||||
message[7*i+1+1] = (uint8_t)1;
|
||||
message[7*i+2+1] = (uint8_t)RGBGetRValue(colors[i]);
|
||||
message[7*i+3+1] = (uint8_t)RGBGetGValue(colors[i]);
|
||||
message[7*i+4+1] = (uint8_t)RGBGetBValue(colors[i]);
|
||||
message[7*i+5+1] = (uint8_t)0;
|
||||
message[7*i+6+1] = (uint8_t)0;
|
||||
}
|
||||
|
||||
external_control_socket.udp_write(reinterpret_cast<char*>(message), size*7+6+1);
|
||||
}
|
||||
else if(model == NANOLEAF_CANVAS_MODEL)
|
||||
{
|
||||
// Insert V2 protocol implementation here.
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafController::StartExternalControl()
|
||||
{
|
||||
json request;
|
||||
request["write"]["command"] = "display";
|
||||
request["write"]["animType"] = "extControl";
|
||||
|
||||
if(model == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
request["write"]["extControlVersion"] = "v1";
|
||||
}
|
||||
else if(model == NANOLEAF_CANVAS_MODEL)
|
||||
{
|
||||
request["write"]["extControlVersion"] = "v2";
|
||||
}
|
||||
|
||||
json response;
|
||||
if(APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request, &response)/100 == 2)
|
||||
{
|
||||
external_control_socket.udp_client(response["streamControlIpAddr"].get<std::string>().c_str(), std::to_string(response["streamControlPort"].get<int>()).c_str());
|
||||
|
||||
selectedEffect = NANOLEAF_DIRECT_MODE_EFFECT_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafController::SelectEffect(std::string effect_name)
|
||||
{
|
||||
json request;
|
||||
request["select"] = effect_name;
|
||||
if(APIRequest("PUT", location, "/api/v1/"+auth_token+"/effects", &request)/100 == 2)
|
||||
{
|
||||
selectedEffect = effect_name;
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafController::SetBrightness(int a_brightness)
|
||||
{
|
||||
json request;
|
||||
request["brightness"]["value"] = a_brightness;
|
||||
if(APIRequest("PUT", location, "/api/v1/"+auth_token+"/state", &request)/100 == 2)
|
||||
{
|
||||
brightness = a_brightness;
|
||||
}
|
||||
}
|
||||
62
Controllers/NanoleafController/NanoleafController.h
Normal file
62
Controllers/NanoleafController/NanoleafController.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*-----------------------------------------*\
|
||||
| NanoleafController.h |
|
||||
| |
|
||||
| API Interface for Nanoleaf devices |
|
||||
| |
|
||||
| Nikita Rushmanov 01/13/2022 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
#include "RGBController.h"
|
||||
#include "net_port.h"
|
||||
|
||||
#define NANOLEAF_DIRECT_MODE_EFFECT_NAME "*Dynamic*"
|
||||
|
||||
#define NANOLEAF_LIGHT_PANELS_MODEL "NL22"
|
||||
#define NANOLEAF_CANVAS_MODEL "NL29"
|
||||
|
||||
class NanoleafController
|
||||
{
|
||||
public:
|
||||
static std::string Pair(std::string address, int port);
|
||||
static void Unpair(std::string address, int port, std::string auth_token);
|
||||
|
||||
NanoleafController(std::string a_address, int a_port, std::string a_auth_token);
|
||||
|
||||
void SelectEffect(std::string effect_name);
|
||||
void StartExternalControl();
|
||||
void SetBrightness(int a_brightness);
|
||||
// Requires External Control to have been started.
|
||||
void UpdateLEDs(std::vector<RGBColor>& colors);
|
||||
|
||||
std::string GetAuthToken() { return auth_token; };
|
||||
std::string GetName() { return name; };
|
||||
std::string GetSerial() { return serial; };
|
||||
std::string GetManufacturer() { return manufacturer; };
|
||||
std::string GetFirmwareVersion() { return firmware_version; };
|
||||
std::string GetModel() { return model; };
|
||||
std::vector<std::string>& GetEffects() { return effects; };
|
||||
std::vector<int>& GetPanelIds() { return panel_ids; };
|
||||
std::string GetSelectedEffect() { return selectedEffect; };
|
||||
int GetBrightness() { return brightness; };
|
||||
|
||||
private:
|
||||
net_port external_control_socket;
|
||||
|
||||
std::string address;
|
||||
int port;
|
||||
std::string location;
|
||||
std::string auth_token;
|
||||
|
||||
std::string name;
|
||||
std::string serial;
|
||||
std::string manufacturer;
|
||||
std::string firmware_version;
|
||||
std::string model;
|
||||
|
||||
std::vector<std::string> effects;
|
||||
std::vector<int> panel_ids;
|
||||
|
||||
std::string selectedEffect;
|
||||
int brightness;
|
||||
};
|
||||
40
Controllers/NanoleafController/NanoleafControllerDetect.cpp
Normal file
40
Controllers/NanoleafController/NanoleafControllerDetect.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "Detector.h"
|
||||
#include "RGBController_Nanoleaf.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "LogManager.h"
|
||||
|
||||
/*----------------------------------------------------------------------------------------*\
|
||||
| |
|
||||
| DetectNanoleafControllers |
|
||||
| |
|
||||
| Connect to paired Nanoleaf devices |
|
||||
| |
|
||||
\*----------------------------------------------------------------------------------------*/
|
||||
|
||||
void DetectNanoleafControllers(std::vector<RGBController*> &rgb_controllers)
|
||||
{
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
|
||||
if(nanoleaf_settings.contains("devices"))
|
||||
{
|
||||
for(json::const_iterator it = nanoleaf_settings["devices"].begin(); it != nanoleaf_settings["devices"].end(); ++it)
|
||||
{
|
||||
const json& device = it.value();
|
||||
|
||||
if(device.contains("ip") && device.contains("port") && device.contains("auth_token"))
|
||||
{
|
||||
try
|
||||
{
|
||||
RGBController_Nanoleaf* rgb_controller = new RGBController_Nanoleaf(device["ip"], device["port"], device["auth_token"]);
|
||||
rgb_controllers.push_back(rgb_controller);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
LOG_DEBUG("[Nanoleaf] Could not connect to device at %s:%s using auth_token %s", device["ip"], device["port"], device["auth_token"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* DetectNanoleafControllers() */
|
||||
|
||||
REGISTER_DETECTOR("Nanoleaf", DetectNanoleafControllers);
|
||||
139
Controllers/NanoleafController/RGBController_Nanoleaf.cpp
Normal file
139
Controllers/NanoleafController/RGBController_Nanoleaf.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*-----------------------------------------*\
|
||||
| RGBController_Nanoleaf.cpp |
|
||||
| |
|
||||
| Generic RGB Interface for Nanoleaf |
|
||||
| |
|
||||
| Nikita Rushmanov 01/13/2022 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#include "RGBController_Nanoleaf.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "LogManager.h"
|
||||
#include <curl/curl.h>
|
||||
#include "json.hpp"
|
||||
using json = nlohmann::json;
|
||||
|
||||
RGBController_Nanoleaf::RGBController_Nanoleaf(std::string a_address, int a_port, std::string a_auth_token) :
|
||||
nanoleaf(a_address, a_port, a_auth_token)
|
||||
{
|
||||
location = a_address+":"+std::to_string(a_port);
|
||||
name = nanoleaf.GetName();
|
||||
serial = nanoleaf.GetSerial();
|
||||
vendor = nanoleaf.GetManufacturer();
|
||||
version = nanoleaf.GetFirmwareVersion();
|
||||
description = nanoleaf.GetModel();
|
||||
type = DEVICE_TYPE_LIGHT;
|
||||
|
||||
// Direct mode currently only supported for Nanoleaf Panels.
|
||||
if(nanoleaf.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
mode Direct;
|
||||
Direct.name = "Direct";
|
||||
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
|
||||
Direct.color_mode = MODE_COLORS_PER_LED;
|
||||
modes.push_back(Direct);
|
||||
|
||||
// Set this effect as current if the name is selected.
|
||||
if(nanoleaf.GetSelectedEffect() == NANOLEAF_DIRECT_MODE_EFFECT_NAME)
|
||||
{
|
||||
// If the direct mode is active, we need to call this method to open the socket.
|
||||
nanoleaf.StartExternalControl();
|
||||
active_mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(std::vector<std::string>::const_iterator it = nanoleaf.GetEffects().begin(); it != nanoleaf.GetEffects().end(); ++it)
|
||||
{
|
||||
mode effect;
|
||||
effect.name = *it;
|
||||
effect.flags = MODE_FLAG_HAS_BRIGHTNESS;
|
||||
effect.color_mode = MODE_COLORS_NONE;
|
||||
effect.brightness_max = 100;
|
||||
effect.brightness_min = 0;
|
||||
effect.brightness = 100;
|
||||
|
||||
modes.push_back(effect);
|
||||
|
||||
// Set this effect as current if the name is selected.
|
||||
if(nanoleaf.GetSelectedEffect() == effect.name)
|
||||
{
|
||||
active_mode = modes.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
SetupZones();
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::SetupZones()
|
||||
{
|
||||
zone led_zone;
|
||||
led_zone.name = "Nanoleaf Layout";
|
||||
led_zone.type = ZONE_TYPE_LINEAR;
|
||||
led_zone.leds_count = nanoleaf.GetPanelIds().size();
|
||||
led_zone.leds_min = led_zone.leds_count;
|
||||
led_zone.leds_max = led_zone.leds_count;
|
||||
led_zone.matrix_map = NULL;
|
||||
|
||||
for(std::vector<int>::const_iterator it = nanoleaf.GetPanelIds().begin(); it != nanoleaf.GetPanelIds().end(); ++it)
|
||||
{
|
||||
led new_led;
|
||||
new_led.name = std::to_string(*it);
|
||||
leds.push_back(new_led);
|
||||
}
|
||||
|
||||
zones.push_back(led_zone);
|
||||
|
||||
SetupColors();
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::ResizeZone(int /*zone*/, int /*new_size*/)
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| This device does not support resizing zones |
|
||||
\*---------------------------------------------------------*/
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::DeviceUpdateLEDs()
|
||||
{
|
||||
if(nanoleaf.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
nanoleaf.UpdateLEDs(colors);
|
||||
}
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::UpdateZoneLEDs(int /*zone*/)
|
||||
{
|
||||
DeviceUpdateLEDs();
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::UpdateSingleLED(int /*led*/)
|
||||
{
|
||||
DeviceUpdateLEDs();
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::SetCustomMode()
|
||||
{
|
||||
if(nanoleaf.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
// Put the Nanoleaf into direct mode.
|
||||
nanoleaf.StartExternalControl();
|
||||
}
|
||||
}
|
||||
|
||||
void RGBController_Nanoleaf::DeviceUpdateMode()
|
||||
{
|
||||
// 0 mode is reserved for Direct mode
|
||||
if(active_mode == 0 && nanoleaf.GetModel() == NANOLEAF_LIGHT_PANELS_MODEL)
|
||||
{
|
||||
nanoleaf.StartExternalControl();
|
||||
}
|
||||
// Update normal effects.
|
||||
else
|
||||
{
|
||||
// Select effect.
|
||||
nanoleaf.SelectEffect(modes[active_mode].name);
|
||||
|
||||
// Update brightness.
|
||||
nanoleaf.SetBrightness(modes[active_mode].brightness);
|
||||
}
|
||||
}
|
||||
32
Controllers/NanoleafController/RGBController_Nanoleaf.h
Normal file
32
Controllers/NanoleafController/RGBController_Nanoleaf.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*-----------------------------------------*\
|
||||
| RGBController_Nanoleaf.h |
|
||||
| |
|
||||
| Generic RGB Interface for Nanoleaf |
|
||||
| |
|
||||
| Nikita Rushmanov 01/13/2022 |
|
||||
\*-----------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
#include "RGBController.h"
|
||||
#include "NanoleafController.h"
|
||||
|
||||
class RGBController_Nanoleaf : public RGBController
|
||||
{
|
||||
|
||||
public:
|
||||
RGBController_Nanoleaf(std::string a_address, int a_port, std::string a_auth_token);
|
||||
|
||||
void SetupZones();
|
||||
|
||||
void ResizeZone(int zone, int new_size);
|
||||
|
||||
void DeviceUpdateLEDs();
|
||||
void UpdateZoneLEDs(int zone);
|
||||
void UpdateSingleLED(int led);
|
||||
|
||||
void SetCustomMode();
|
||||
void DeviceUpdateMode();
|
||||
|
||||
private:
|
||||
NanoleafController nanoleaf;
|
||||
};
|
||||
28
OpenRGB.pro
28
OpenRGB.pro
|
|
@ -64,6 +64,7 @@ INCLUDEPATH +=
|
|||
dependencies/json/ \
|
||||
dependencies/libe131/src/ \
|
||||
dependencies/libcmmk/include/ \
|
||||
dependencies/mdns \
|
||||
i2c_smbus/ \
|
||||
i2c_tools/ \
|
||||
net_port/ \
|
||||
|
|
@ -130,6 +131,7 @@ INCLUDEPATH +=
|
|||
Controllers/MSIMysticLightController/ \
|
||||
Controllers/MSIOptixController/ \
|
||||
Controllers/MSIRGBController/ \
|
||||
Controllers/NanoleafController/ \
|
||||
Controllers/NZXTHue2Controller/ \
|
||||
Controllers/NZXTHuePlusController/ \
|
||||
Controllers/NZXTKrakenController/ \
|
||||
|
|
@ -207,6 +209,9 @@ HEADERS +=
|
|||
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.h \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.h \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsPage.h \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsEntry.h \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.h \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafScanningThread.h \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsEntry.h \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsPage.h \
|
||||
qt/OpenRGBPhilipsWizSettingsPage/OpenRGBPhilipsWizSettingsEntry.h \
|
||||
|
|
@ -290,7 +295,7 @@ HEADERS +=
|
|||
Controllers/CorsairLightingNodeController/CorsairLightingNodeController.h \
|
||||
Controllers/CorsairLightingNodeController/RGBController_CorsairLightingNode.h \
|
||||
Controllers/CorsairPeripheralController/CorsairPeripheralController.h \
|
||||
Controllers/CorsairPeripheralController/CorsairK100Controller.h \
|
||||
Controllers/CorsairPeripheralController/CorsairK100Controller.h \
|
||||
Controllers/CorsairPeripheralController/CorsairK55RGBPROController.h \
|
||||
Controllers/CorsairPeripheralController/CorsairK65MiniController.h \
|
||||
Controllers/CorsairPeripheralController/RGBController_CorsairPeripheral.h \
|
||||
|
|
@ -324,7 +329,7 @@ HEADERS +=
|
|||
Controllers/ENESMBusController/ENESMBusController.h \
|
||||
Controllers/ENESMBusController/RGBController_ENESMBus.h \
|
||||
Controllers/ENESMBusController/ENESMBusInterface/ENESMBusInterface.h \
|
||||
Controllers/ENESMBusController/ENESMBusInterface/ENESMBusInterface_i2c_smbus.h \
|
||||
Controllers/ENESMBusController/ENESMBusInterface/ENESMBusInterface_i2c_smbus.h \
|
||||
Controllers/EspurnaController/EspurnaController.h \
|
||||
Controllers/EspurnaController/RGBController_Espurna.h \
|
||||
Controllers/EVGAGP102GPUController/EVGAGP102Controller.h \
|
||||
|
|
@ -406,7 +411,7 @@ HEADERS +=
|
|||
Controllers/LogitechController/LogitechG560Controller.h \
|
||||
Controllers/LogitechController/LogitechG933Controller.h \
|
||||
Controllers/LogitechController/LogitechG810Controller.h \
|
||||
Controllers/LogitechController/LogitechGProKeyboardController.h \
|
||||
Controllers/LogitechController/LogitechGProKeyboardController.h \
|
||||
Controllers/LogitechController/LogitechG910Controller.h \
|
||||
Controllers/LogitechController/LogitechG815Controller.h \
|
||||
Controllers/LogitechController/LogitechG915Controller.h \
|
||||
|
|
@ -439,8 +444,10 @@ HEADERS +=
|
|||
Controllers/MSIOptixController/MSIOptixController.h \
|
||||
Controllers/MSIOptixController/RGBController_MSIOptix.h \
|
||||
Controllers/MSIRGBController/MSIRGBController.h \
|
||||
Controllers/NanoleafController/NanoleafController.h \
|
||||
Controllers/MSIRGBController/RGBController_MSIRGB.h \
|
||||
Controllers/NvidiaESAController/NvidiaESAController.h \
|
||||
Controllers/NanoleafController/RGBController_Nanoleaf.h \
|
||||
Controllers/NvidiaESAController/RGBController_NvidiaESA.h \
|
||||
Controllers/NZXTHue2Controller/NZXTHue2Controller.h \
|
||||
Controllers/NZXTHue2Controller/RGBController_NZXTHue2.h \
|
||||
|
|
@ -631,6 +638,9 @@ SOURCES +=
|
|||
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.cpp \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.cpp \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsPage.cpp \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsEntry.cpp \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.cpp \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafScanningThread.cpp \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsEntry.cpp \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsPage.cpp \
|
||||
qt/OpenRGBPhilipsWizSettingsPage/OpenRGBPhilipsWizSettingsEntry.cpp \
|
||||
|
|
@ -773,7 +783,7 @@ SOURCES +=
|
|||
Controllers/ENESMBusController/ENESMBusController.cpp \
|
||||
Controllers/ENESMBusController/ENESMBusControllerDetect.cpp \
|
||||
Controllers/ENESMBusController/RGBController_ENESMBus.cpp \
|
||||
Controllers/ENESMBusController/ENESMBusInterface/ENESMBusInterface_i2c_smbus.cpp \
|
||||
Controllers/ENESMBusController/ENESMBusInterface/ENESMBusInterface_i2c_smbus.cpp \
|
||||
Controllers/EspurnaController/EspurnaController.cpp \
|
||||
Controllers/EspurnaController/EspurnaControllerDetect.cpp \
|
||||
Controllers/EspurnaController/RGBController_Espurna.cpp \
|
||||
|
|
@ -922,6 +932,9 @@ SOURCES +=
|
|||
Controllers/MSIRGBController/MSIRGBController.cpp \
|
||||
Controllers/MSIRGBController/MSIRGBControllerDetect.cpp \
|
||||
Controllers/MSIRGBController/RGBController_MSIRGB.cpp \
|
||||
Controllers/NanoleafController/NanoleafController.cpp \
|
||||
Controllers/NanoleafController/NanoleafControllerDetect.cpp \
|
||||
Controllers/NanoleafController/RGBController_Nanoleaf.cpp \
|
||||
Controllers/NvidiaESAController/NvidiaESAController.cpp \
|
||||
Controllers/NvidiaESAController/NvidiaESAControllerDetect.cpp \
|
||||
Controllers/NvidiaESAController/RGBController_NvidiaESA.cpp \
|
||||
|
|
@ -1088,6 +1101,8 @@ FORMS +=
|
|||
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.ui \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.ui \
|
||||
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsPage.ui \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.ui \
|
||||
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsEntry.ui \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsEntry.ui \
|
||||
qt/OpenRGBPhilipsHueSettingsPage/OpenRGBPhilipsHueSettingsPage.ui \
|
||||
qt/OpenRGBPhilipsWizSettingsPage/OpenRGBPhilipsWizSettingsEntry.ui \
|
||||
|
|
@ -1101,6 +1116,9 @@ FORMS +=
|
|||
qt/OpenRGBZonesBulkResizer.ui \
|
||||
qt/TabLabel.ui \
|
||||
|
||||
LIBS += \
|
||||
-lcurl \
|
||||
|
||||
#-----------------------------------------------------------------------------------------------#
|
||||
# Windows-specific Configuration #
|
||||
#-----------------------------------------------------------------------------------------------#
|
||||
|
|
@ -1540,7 +1558,7 @@ macx {
|
|||
HEADERS += \
|
||||
AutoStart/AutoStart-MacOS.h \
|
||||
qt/macutils.h \
|
||||
|
||||
|
||||
SOURCES += \
|
||||
dependencies/hueplusplus-1.0.0/src/LinHttpHandler.cpp \
|
||||
serial_port/find_usb_serial_port_linux.cpp \
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ There have been two instances of hardware damage in OpenRGB's development and we
|
|||
* Effects Engine Plugin (by herosilas12, morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBEffectsPlugin
|
||||
* OpenRGB Visual Map Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBVisualMapPlugin
|
||||
* Scheduler Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBSchedulerPlugin
|
||||
* Skin Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/openrgbskinplugin
|
||||
* Skin Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/openrgbskinplugin
|
||||
* Hardware Sync Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBHardwareSyncPlugin
|
||||
* Http Hook Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBHttpHookPlugin
|
||||
* Razer extras Plugin (by morg): https://gitlab.com/OpenRGBDevelopers/OpenRGBRazerExtrasPlugin
|
||||
|
|
@ -308,7 +308,9 @@ There have been two instances of hardware damage in OpenRGB's development and we
|
|||
* AMD ADL Libraries: https://github.com/GPUOpen-LibrariesAndSDKs/display-library
|
||||
* libcmmk: https://github.com/chmod222/libcmmk
|
||||
* hueplusplus: https://github.com/enwi/hueplusplus
|
||||
|
||||
* httplib: https://github.com/yhirose/cpp-httplib
|
||||
* mdns: https://github.com/mjansson/mdns
|
||||
|
||||
## Projects Researched
|
||||
|
||||
While no code from these projects directly made its way into OpenRGB, these projects have been invaluable resources for protocol information.
|
||||
|
|
|
|||
1539
dependencies/mdns/mdns.h
vendored
Normal file
1539
dependencies/mdns/mdns.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -174,14 +174,14 @@ OpenRGBDialog2::OpenRGBDialog2(QWidget *parent) : QMainWindow(parent), ui(new Op
|
|||
if(!ui_settings.contains("geometry"))
|
||||
{
|
||||
json geometry_settings;
|
||||
|
||||
|
||||
geometry_settings["load_geometry"] = false;
|
||||
geometry_settings["save_on_exit"] = false;
|
||||
geometry_settings["x"] = 0;
|
||||
geometry_settings["y"] = 0;
|
||||
geometry_settings["width"] = 0;
|
||||
geometry_settings["height"] = 0;
|
||||
|
||||
|
||||
ui_settings["geometry"] = geometry_settings;
|
||||
|
||||
settings_manager->SetSettings(ui_string, ui_settings);
|
||||
|
|
@ -192,7 +192,7 @@ OpenRGBDialog2::OpenRGBDialog2(QWidget *parent) : QMainWindow(parent), ui(new Op
|
|||
| If geometry information exists in settings, apply it |
|
||||
\*-----------------------------------------------------*/
|
||||
bool load_geometry = false;
|
||||
|
||||
|
||||
if(ui_settings["geometry"].contains("load_geometry"))
|
||||
{
|
||||
load_geometry = ui_settings["geometry"]["load_geometry"].get<bool>();
|
||||
|
|
@ -422,13 +422,18 @@ OpenRGBDialog2::OpenRGBDialog2(QWidget *parent) : QMainWindow(parent), ui(new Op
|
|||
\*-----------------------------------------------------*/
|
||||
AddYeelightSettingsPage();
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Add the Nanoleaf settings page |
|
||||
\*-----------------------------------------------------*/
|
||||
AddNanoleafSettingsPage();
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Add the SMBus Tools page if enabled |
|
||||
\*-----------------------------------------------------*/
|
||||
if(ShowI2CTools)
|
||||
{
|
||||
AddI2CToolsPage();
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| If log console is enabled in settings, enable it |
|
||||
|
|
@ -813,6 +818,34 @@ void OpenRGBDialog2::AddYeelightSettingsPage()
|
|||
ui->SettingsTabBar->tabBar()->setTabButton(ui->SettingsTabBar->tabBar()->count() - 1, QTabBar::LeftSide, SettingsTabLabel);
|
||||
}
|
||||
|
||||
void OpenRGBDialog2::AddNanoleafSettingsPage()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Create the Settings page |
|
||||
\*-----------------------------------------------------*/
|
||||
NanoleafSettingsPage = new OpenRGBNanoleafSettingsPage();
|
||||
|
||||
ui->SettingsTabBar->addTab(NanoleafSettingsPage, "");
|
||||
|
||||
QString SettingsLabelString;
|
||||
|
||||
if(OpenRGBThemeManager::IsDarkTheme())
|
||||
{
|
||||
SettingsLabelString = "light_dark.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsLabelString = "light.png";
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Create the tab label |
|
||||
\*-----------------------------------------------------*/
|
||||
TabLabel* SettingsTabLabel = new TabLabel(SettingsLabelString, "Nanoleaf Devices");
|
||||
|
||||
ui->SettingsTabBar->tabBar()->setTabButton(ui->SettingsTabBar->tabBar()->count() - 1, QTabBar::LeftSide, SettingsTabLabel);
|
||||
}
|
||||
|
||||
void OpenRGBDialog2::AddPlugin(OpenRGBPluginEntry* plugin)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "OpenRGBQMKORGBSettingsPage/OpenRGBQMKORGBSettingsPage.h"
|
||||
#include "OpenRGBSerialSettingsPage/OpenRGBSerialSettingsPage.h"
|
||||
#include "OpenRGBYeelightSettingsPage/OpenRGBYeelightSettingsPage.h"
|
||||
#include "OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include <vector>
|
||||
|
|
@ -78,6 +79,7 @@ private:
|
|||
OpenRGBQMKORGBSettingsPage *QMKORGBSettingsPage;
|
||||
OpenRGBSerialSettingsPage *SerialSettingsPage;
|
||||
OpenRGBYeelightSettingsPage *YeelightSettingsPage;
|
||||
OpenRGBNanoleafSettingsPage *NanoleafSettingsPage;
|
||||
|
||||
bool ShowI2CTools = false;
|
||||
|
||||
|
|
@ -103,6 +105,7 @@ private:
|
|||
void AddQMKORGBSettingsPage();
|
||||
void AddSerialSettingsPage();
|
||||
void AddYeelightSettingsPage();
|
||||
void AddNanoleafSettingsPage();
|
||||
void AddPluginsPage();
|
||||
void AddConsolePage();
|
||||
|
||||
|
|
|
|||
438
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafScanningThread.cpp
Normal file
438
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafScanningThread.cpp
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
#ifdef _WIN32
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#include "mdns.h"
|
||||
|
||||
#include "OpenRGBNanoleafScanningThread.h"
|
||||
|
||||
static char namebuffer[256];
|
||||
|
||||
static struct sockaddr_in service_address_ipv4;
|
||||
static struct sockaddr_in6 service_address_ipv6;
|
||||
|
||||
static int has_ipv4;
|
||||
static int has_ipv6;
|
||||
|
||||
static mdns_string_t ipv4_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in* addr, size_t addrlen)
|
||||
{
|
||||
char host[NI_MAXHOST] = {0};
|
||||
char service[NI_MAXSERV] = {0};
|
||||
int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, NI_MAXHOST,
|
||||
service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
|
||||
int len = 0;
|
||||
if (ret == 0)
|
||||
{
|
||||
len = snprintf(buffer, capacity, "%s", host);
|
||||
}
|
||||
if (len >= (int)capacity)
|
||||
len = (int)capacity - 1;
|
||||
mdns_string_t str;
|
||||
str.str = buffer;
|
||||
str.length = len;
|
||||
return str;
|
||||
}
|
||||
|
||||
static mdns_string_t ipv6_address_to_string(char* buffer, size_t capacity, const struct sockaddr_in6* addr, size_t addrlen)
|
||||
{
|
||||
char host[NI_MAXHOST] = {0};
|
||||
char service[NI_MAXSERV] = {0};
|
||||
int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, NI_MAXHOST,
|
||||
service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
|
||||
int len = 0;
|
||||
if (ret == 0)
|
||||
{
|
||||
if (addr->sin6_port != 0)
|
||||
len = snprintf(buffer, capacity, "[%s]:%s", host, service);
|
||||
else
|
||||
len = snprintf(buffer, capacity, "%s", host);
|
||||
}
|
||||
if (len >= (int)capacity)
|
||||
len = (int)capacity - 1;
|
||||
mdns_string_t str;
|
||||
str.str = buffer;
|
||||
str.length = len;
|
||||
return str;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Open sockets for sending one-shot multicast queries |
|
||||
| from an ephemeral port |
|
||||
\*-----------------------------------------------------*/
|
||||
static int open_client_sockets(int* sockets, int max_sockets, int port)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| When sending, each socket can only send to one |
|
||||
| network interface from an ephemeral port, thus we |
|
||||
| need to open one socket for each interface and |
|
||||
| address family |
|
||||
\*-----------------------------------------------------*/
|
||||
int num_sockets = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
IP_ADAPTER_ADDRESSES* adapter_address = 0;
|
||||
ULONG address_size = 8000;
|
||||
unsigned int ret;
|
||||
unsigned int num_retries = 4;
|
||||
do
|
||||
{
|
||||
adapter_address = (IP_ADAPTER_ADDRESSES*)malloc(address_size);
|
||||
ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0,
|
||||
adapter_address, &address_size);
|
||||
if (ret == ERROR_BUFFER_OVERFLOW)
|
||||
{
|
||||
free(adapter_address);
|
||||
adapter_address = 0;
|
||||
address_size *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (num_retries-- > 0);
|
||||
|
||||
if (!adapter_address || (ret != NO_ERROR))
|
||||
{
|
||||
free(adapter_address);
|
||||
return num_sockets;
|
||||
}
|
||||
|
||||
int first_ipv4 = 1;
|
||||
int first_ipv6 = 1;
|
||||
for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter; adapter = adapter->Next)
|
||||
{
|
||||
if (adapter->TunnelType == TUNNEL_TYPE_TEREDO)
|
||||
continue;
|
||||
if (adapter->OperStatus != IfOperStatusUp)
|
||||
continue;
|
||||
|
||||
for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast;
|
||||
unicast = unicast->Next)
|
||||
{
|
||||
if (unicast->Address.lpSockaddr->sa_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* saddr = (struct sockaddr_in*)unicast->Address.lpSockaddr;
|
||||
if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) ||
|
||||
(saddr->sin_addr.S_un.S_un_b.s_b2 != 0) ||
|
||||
(saddr->sin_addr.S_un.S_un_b.s_b3 != 0) ||
|
||||
(saddr->sin_addr.S_un.S_un_b.s_b4 != 1))
|
||||
{
|
||||
int log_addr = 0;
|
||||
if (first_ipv4)
|
||||
{
|
||||
service_address_ipv4 = *saddr;
|
||||
first_ipv4 = 0;
|
||||
log_addr = 1;
|
||||
}
|
||||
has_ipv4 = 1;
|
||||
if (num_sockets < max_sockets)
|
||||
{
|
||||
saddr->sin_port = htons((unsigned short)port);
|
||||
int sock = mdns_socket_open_ipv4(saddr);
|
||||
if (sock >= 0)
|
||||
{
|
||||
sockets[num_sockets++] = sock;
|
||||
log_addr = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_addr = 0;
|
||||
}
|
||||
}
|
||||
if (log_addr)
|
||||
{
|
||||
char buffer[128];
|
||||
mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
|
||||
sizeof(struct sockaddr_in));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (unicast->Address.lpSockaddr->sa_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* saddr = (struct sockaddr_in6*)unicast->Address.lpSockaddr;
|
||||
static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1};
|
||||
static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
|
||||
if ((unicast->DadState == NldsPreferred) &&
|
||||
memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
|
||||
memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16))
|
||||
{
|
||||
int log_addr = 0;
|
||||
if (first_ipv6)
|
||||
{
|
||||
service_address_ipv6 = *saddr;
|
||||
first_ipv6 = 0;
|
||||
log_addr = 1;
|
||||
}
|
||||
has_ipv6 = 1;
|
||||
if (num_sockets < max_sockets)
|
||||
{
|
||||
saddr->sin6_port = htons((unsigned short)port);
|
||||
int sock = mdns_socket_open_ipv6(saddr);
|
||||
if (sock >= 0)
|
||||
{
|
||||
sockets[num_sockets++] = sock;
|
||||
log_addr = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_addr = 0;
|
||||
}
|
||||
}
|
||||
if (log_addr)
|
||||
{
|
||||
char buffer[128];
|
||||
mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
|
||||
sizeof(struct sockaddr_in6));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(adapter_address);
|
||||
|
||||
#else
|
||||
|
||||
struct ifaddrs* ifaddr = 0;
|
||||
struct ifaddrs* ifa = 0;
|
||||
|
||||
getifaddrs(&ifaddr);
|
||||
|
||||
int first_ipv4 = 1;
|
||||
int first_ipv6 = 1;
|
||||
for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (!ifa->ifa_addr)
|
||||
continue;
|
||||
|
||||
if (ifa->ifa_addr->sa_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* saddr = (struct sockaddr_in*)ifa->ifa_addr;
|
||||
if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK))
|
||||
{
|
||||
int log_addr = 0;
|
||||
if (first_ipv4)
|
||||
{
|
||||
service_address_ipv4 = *saddr;
|
||||
first_ipv4 = 0;
|
||||
log_addr = 1;
|
||||
}
|
||||
has_ipv4 = 1;
|
||||
if (num_sockets < max_sockets)
|
||||
{
|
||||
saddr->sin_port = htons(port);
|
||||
int sock = mdns_socket_open_ipv4(saddr);
|
||||
if (sock >= 0)
|
||||
{
|
||||
sockets[num_sockets++] = sock;
|
||||
log_addr = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_addr = 0;
|
||||
}
|
||||
}
|
||||
if (log_addr)
|
||||
{
|
||||
char buffer[128];
|
||||
mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
|
||||
sizeof(struct sockaddr_in));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ifa->ifa_addr->sa_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* saddr = (struct sockaddr_in6*)ifa->ifa_addr;
|
||||
static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1};
|
||||
static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
|
||||
if (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
|
||||
memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16))
|
||||
{
|
||||
int log_addr = 0;
|
||||
if (first_ipv6)
|
||||
{
|
||||
service_address_ipv6 = *saddr;
|
||||
first_ipv6 = 0;
|
||||
log_addr = 1;
|
||||
}
|
||||
has_ipv6 = 1;
|
||||
if (num_sockets < max_sockets)
|
||||
{
|
||||
saddr->sin6_port = htons(port);
|
||||
int sock = mdns_socket_open_ipv6(saddr);
|
||||
if (sock >= 0)
|
||||
{
|
||||
sockets[num_sockets++] = sock;
|
||||
log_addr = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_addr = 0;
|
||||
}
|
||||
}
|
||||
if (log_addr)
|
||||
{
|
||||
char buffer[128];
|
||||
mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
|
||||
sizeof(struct sockaddr_in6));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
|
||||
#endif
|
||||
|
||||
return num_sockets;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Callback handling parsing answers to queries sent |
|
||||
\*-----------------------------------------------------*/
|
||||
static int query_callback(
|
||||
int sock,
|
||||
[[maybe_unused]] const struct sockaddr* from,
|
||||
[[maybe_unused]] size_t addrlen,
|
||||
[[maybe_unused]] mdns_entry_type_t entry,
|
||||
uint16_t query_id,
|
||||
uint16_t rtype,
|
||||
[[maybe_unused]] uint16_t rclass,
|
||||
[[maybe_unused]] uint32_t ttl,
|
||||
const void* data,
|
||||
size_t size,
|
||||
[[maybe_unused]] size_t name_offset,
|
||||
size_t name_length,
|
||||
size_t record_offset,
|
||||
size_t record_length,
|
||||
void* user_data)
|
||||
{
|
||||
(void)sizeof(sock);
|
||||
(void)sizeof(query_id);
|
||||
(void)sizeof(name_length);
|
||||
(void)sizeof(user_data);
|
||||
|
||||
if (rtype == MDNS_RECORDTYPE_A)
|
||||
{
|
||||
struct sockaddr_in address;
|
||||
mdns_record_parse_a(data, size, record_offset, record_length, &address);
|
||||
|
||||
if (address.sin_port == 0)
|
||||
address.sin_port = 16021; // Default Nanoleaf port.
|
||||
|
||||
mdns_string_t addrstr =
|
||||
ipv4_address_to_string(namebuffer, sizeof(namebuffer), &address, sizeof(address));
|
||||
|
||||
// printf("A %.*s:%u\n", MDNS_STRING_FORMAT(addrstr), address.sin_port);
|
||||
|
||||
(static_cast<OpenRGBNanoleafScanningThread*>(user_data))->EmitDeviceFound(addrstr.str, address.sin_port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OpenRGBNanoleafScanningThread::EmitDeviceFound(QString address, int port)
|
||||
{
|
||||
emit DeviceFound(address, port);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Send a mDNS query |
|
||||
\*-----------------------------------------------------*/
|
||||
int OpenRGBNanoleafScanningThread::SendMDNSQuery()
|
||||
{
|
||||
const char* service = "_nanoleafapi._tcp.local.";
|
||||
mdns_record_type record = MDNS_RECORDTYPE_PTR;
|
||||
|
||||
int sockets[32];
|
||||
int query_id[32];
|
||||
int num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0);
|
||||
if (num_sockets <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t capacity = 2048;
|
||||
void* buffer = malloc(capacity);
|
||||
size_t records;
|
||||
|
||||
const char* record_name = "PTR";
|
||||
if (record == MDNS_RECORDTYPE_SRV)
|
||||
record_name = "SRV";
|
||||
else if (record == MDNS_RECORDTYPE_A)
|
||||
record_name = "A";
|
||||
else if (record == MDNS_RECORDTYPE_AAAA)
|
||||
record_name = "AAAA";
|
||||
else
|
||||
record = MDNS_RECORDTYPE_PTR;
|
||||
|
||||
for (int isock = 0; isock < num_sockets; ++isock)
|
||||
{
|
||||
query_id[isock] =
|
||||
mdns_query_send(sockets[isock], record, service, strlen(service), buffer, capacity, 0);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| This is a simple implementation that loops for |
|
||||
| 5 seconds or as long as we get replies |
|
||||
\*-----------------------------------------------------*/
|
||||
int res;
|
||||
do
|
||||
{
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 5;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int nfds = 0;
|
||||
fd_set readfs;
|
||||
FD_ZERO(&readfs);
|
||||
for (int isock = 0; isock < num_sockets; ++isock)
|
||||
{
|
||||
if (sockets[isock] >= nfds)
|
||||
nfds = sockets[isock] + 1;
|
||||
FD_SET(sockets[isock], &readfs);
|
||||
}
|
||||
|
||||
records = 0;
|
||||
res = select(nfds, &readfs, 0, 0, &timeout);
|
||||
if (res > 0)
|
||||
{
|
||||
for (int isock = 0; isock < num_sockets; ++isock)
|
||||
{
|
||||
if (FD_ISSET(sockets[isock], &readfs))
|
||||
{
|
||||
records += mdns_query_recv(sockets[isock], buffer, capacity, query_callback,
|
||||
this, query_id[isock]);
|
||||
}
|
||||
FD_SET(sockets[isock], &readfs);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (res > 0);
|
||||
|
||||
free(buffer);
|
||||
|
||||
for (int isock = 0; isock < num_sockets; ++isock)
|
||||
mdns_socket_close(sockets[isock]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OpenRGBNanoleafScanningThread::run()
|
||||
{
|
||||
SendMDNSQuery();
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef OPENRGBNanoleafScanningThread_H
|
||||
#define OPENRGBNanoleafScanningThread_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class OpenRGBNanoleafScanningThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
void run();
|
||||
|
||||
int SendMDNSQuery();
|
||||
|
||||
signals:
|
||||
void DeviceFound(QString address, int port);
|
||||
|
||||
public:
|
||||
void EmitDeviceFound(QString address, int port);
|
||||
};
|
||||
|
||||
#endif // OPENRGBNanoleafScanningThread_H
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#include "OpenRGBNanoleafSettingsEntry.h"
|
||||
#include "ui_OpenRGBNanoleafSettingsEntry.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "NanoleafController.h"
|
||||
#include "json.hpp"
|
||||
using json = nlohmann::json;
|
||||
|
||||
using namespace Ui;
|
||||
|
||||
OpenRGBNanoleafSettingsEntry::OpenRGBNanoleafSettingsEntry(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OpenRGBNanoleafSettingsEntryUi),
|
||||
paired(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
OpenRGBNanoleafSettingsEntry::OpenRGBNanoleafSettingsEntry(QString a_address, int a_port) :
|
||||
OpenRGBNanoleafSettingsEntry(nullptr)
|
||||
{
|
||||
address = a_address;
|
||||
port = a_port;
|
||||
const std::string location = address.toStdString()+":"+std::to_string(port);
|
||||
|
||||
ui->IPValue->setText(address);
|
||||
ui->PortValue->setText(QString::fromStdString(std::to_string(a_port)));
|
||||
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
|
||||
if(nanoleaf_settings["devices"].contains(location) &&
|
||||
nanoleaf_settings["devices"][location].contains("auth_token") &&
|
||||
nanoleaf_settings["devices"][location]["auth_token"].size())
|
||||
{
|
||||
paired = true;
|
||||
auth_token = nanoleaf_settings["devices"][location]["auth_token"];
|
||||
ui->AuthKeyValue->setText(QString::fromStdString(auth_token));
|
||||
ui->PairButton->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->UnpairButton->hide();
|
||||
}
|
||||
}
|
||||
|
||||
OpenRGBNanoleafSettingsEntry::~OpenRGBNanoleafSettingsEntry()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OpenRGBNanoleafSettingsEntry::on_PairButton_clicked()
|
||||
{
|
||||
try
|
||||
{
|
||||
auth_token = NanoleafController::Pair(address.toStdString(), port);
|
||||
|
||||
// Save auth token.
|
||||
const std::string location = address.toStdString()+":"+std::to_string(port);
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
nanoleaf_settings["devices"][location]["ip"] = address.toStdString();
|
||||
nanoleaf_settings["devices"][location]["port"] = port;
|
||||
nanoleaf_settings["devices"][location]["auth_token"] = auth_token;
|
||||
ResourceManager::get()->GetSettingsManager()->SetSettings("NanoleafDevices", nanoleaf_settings);
|
||||
ResourceManager::get()->GetSettingsManager()->SaveSettings();
|
||||
|
||||
// Update UI.
|
||||
paired = true;
|
||||
ui->AuthKeyValue->setText(QString::fromStdString(auth_token));
|
||||
ui->PairButton->hide();
|
||||
ui->UnpairButton->show();
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
paired = false;
|
||||
ui->AuthKeyValue->setText("PAIRING FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
void OpenRGBNanoleafSettingsEntry::on_UnpairButton_clicked()
|
||||
{
|
||||
NanoleafController::Unpair(address.toStdString(), port, auth_token);
|
||||
|
||||
const std::string location = address.toStdString()+":"+std::to_string(port);
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
nanoleaf_settings["devices"].erase(location);
|
||||
ResourceManager::get()->GetSettingsManager()->SetSettings("NanoleafDevices", nanoleaf_settings);
|
||||
ResourceManager::get()->GetSettingsManager()->SaveSettings();
|
||||
|
||||
paired = false;
|
||||
ui->AuthKeyValue->setText("");
|
||||
ui->PairButton->show();
|
||||
ui->UnpairButton->hide();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef OPENRGBNanoleafSETTINGSENTRY_H
|
||||
#define OPENRGBNanoleafSETTINGSENTRY_H
|
||||
|
||||
#include "ui_OpenRGBNanoleafSettingsEntry.h"
|
||||
#include "OpenRGBNanoleafScanningThread.h"
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class OpenRGBNanoleafSettingsEntry;
|
||||
}
|
||||
|
||||
class Ui::OpenRGBNanoleafSettingsEntry : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OpenRGBNanoleafSettingsEntry(QWidget *parent = nullptr);
|
||||
OpenRGBNanoleafSettingsEntry(QString a_address, int a_port);
|
||||
~OpenRGBNanoleafSettingsEntry();
|
||||
Ui::OpenRGBNanoleafSettingsEntryUi *ui;
|
||||
|
||||
private slots:
|
||||
void on_UnpairButton_clicked();
|
||||
void on_PairButton_clicked();
|
||||
|
||||
private:
|
||||
QString address;
|
||||
int port;
|
||||
std::string auth_token;
|
||||
bool paired;
|
||||
};
|
||||
|
||||
#endif // OPENRGBNanoleafSETTINGSENTRY_H
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OpenRGBNanoleafSettingsEntryUi</class>
|
||||
<widget class="QWidget" name="OpenRGBNanoleafSettingsEntryUi">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>287</width>
|
||||
<height>207</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="IPLabel">
|
||||
<property name="text">
|
||||
<string>IP:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="4">
|
||||
<widget class="QLabel" name="IPValue">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="PortLabel">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="PortValue">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="AuthKeyLabel">
|
||||
<property name="text">
|
||||
<string>Auth Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="AuthKeyValue">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="UnpairButton">
|
||||
<property name="text">
|
||||
<string>Unpair</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="PairButton">
|
||||
<property name="text">
|
||||
<string>Pair</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
#include "OpenRGBNanoleafSettingsPage.h"
|
||||
#include "ui_OpenRGBNanoleafSettingsPage.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "json.hpp"
|
||||
using json = nlohmann::json;
|
||||
|
||||
using namespace Ui;
|
||||
|
||||
OpenRGBNanoleafSettingsPage::OpenRGBNanoleafSettingsPage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OpenRGBNanoleafSettingsPageUi)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
|
||||
if(nanoleaf_settings.contains("devices"))
|
||||
{
|
||||
for(json::const_iterator it = nanoleaf_settings["devices"].begin(); it != nanoleaf_settings["devices"].end(); ++it)
|
||||
{
|
||||
const json& device = it.value();
|
||||
const std::string& location = it.key();
|
||||
|
||||
if(device.contains("ip") && device.contains("port"))
|
||||
{
|
||||
OpenRGBNanoleafSettingsEntry* entry = new OpenRGBNanoleafSettingsEntry(QString::fromStdString(device["ip"]), device["port"]);
|
||||
|
||||
entries[location] = entry;
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem;
|
||||
|
||||
item->setSizeHint(entry->sizeHint());
|
||||
|
||||
ui->NanoleafDeviceList->addItem(item);
|
||||
ui->NanoleafDeviceList->setItemWidget(item, entry);
|
||||
ui->NanoleafDeviceList->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OpenRGBNanoleafSettingsPage::~OpenRGBNanoleafSettingsPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void Ui::OpenRGBNanoleafSettingsPage::on_ScanForNanoleafDevicesButton_clicked()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Create a worker thread for the mDNS query and hookup |
|
||||
| callbacks for when it finds devices |
|
||||
\*-----------------------------------------------------*/
|
||||
OpenRGBNanoleafScanningThread *scanThread = new OpenRGBNanoleafScanningThread;
|
||||
|
||||
connect(scanThread, SIGNAL(DeviceFound(QString, int)),
|
||||
SLOT(on_DeviceFound(QString, int)));
|
||||
|
||||
connect(scanThread, SIGNAL(finished()),
|
||||
scanThread, SLOT(deleteLater()));
|
||||
|
||||
scanThread->start();
|
||||
}
|
||||
|
||||
void Ui::OpenRGBNanoleafSettingsPage::on_DeviceFound(QString address, int port)
|
||||
{
|
||||
std::string location = address.toStdString()+":"+std::to_string(port);
|
||||
|
||||
if(entries.find(location) == entries.end())
|
||||
{
|
||||
OpenRGBNanoleafSettingsEntry* entry = new OpenRGBNanoleafSettingsEntry(address, port);
|
||||
|
||||
entries[location] = entry;
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem;
|
||||
|
||||
item->setSizeHint(entry->sizeHint());
|
||||
|
||||
ui->NanoleafDeviceList->addItem(item);
|
||||
ui->NanoleafDeviceList->setItemWidget(item, entry);
|
||||
ui->NanoleafDeviceList->show();
|
||||
}
|
||||
}
|
||||
31
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.h
Normal file
31
qt/OpenRGBNanoleafSettingsPage/OpenRGBNanoleafSettingsPage.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef OPENRGBNanoleafSETTINGSPAGE_H
|
||||
#define OPENRGBNanoleafSETTINGSPAGE_H
|
||||
|
||||
#include "ui_OpenRGBNanoleafSettingsPage.h"
|
||||
#include <QWidget>
|
||||
|
||||
#include "OpenRGBNanoleafSettingsEntry.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class OpenRGBNanoleafSettingsPage;
|
||||
}
|
||||
|
||||
class Ui::OpenRGBNanoleafSettingsPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OpenRGBNanoleafSettingsPage(QWidget *parent = nullptr);
|
||||
~OpenRGBNanoleafSettingsPage();
|
||||
|
||||
private slots:
|
||||
void on_ScanForNanoleafDevicesButton_clicked();
|
||||
void on_DeviceFound(QString address, int port);
|
||||
|
||||
private:
|
||||
Ui::OpenRGBNanoleafSettingsPageUi *ui;
|
||||
std::map<std::string, OpenRGBNanoleafSettingsEntry*> entries;
|
||||
};
|
||||
|
||||
#endif // OPENRGBNanoleafSETTINGSPAGE_H
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OpenRGBNanoleafSettingsPageUi</class>
|
||||
<widget class="QWidget" name="OpenRGBNanoleafSettingsPageUi">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QPushButton" name="ScanForNanoleafDevicesButton">
|
||||
<property name="text">
|
||||
<string>Scan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QListWidget" name="NanoleafDeviceList">
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="SyncLabel">
|
||||
<property name="text">
|
||||
<string>To pair, hold the on-off button down for 5-7 seconds until the LED starts flashing in a pattern, then click the "Pair" button within 30 seconds.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Loading…
Add table
Add a link
Reference in a new issue