Publish LumaOps source
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafNewDeviceDialog.cpp |
|
||||
| |
|
||||
| User interface for OpenRGB Nanoleaf dialog |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include "ResourceManager.h"
|
||||
#include "NanoleafNewDeviceDialog.h"
|
||||
#include "ui_NanoleafNewDeviceDialog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <QSettings>
|
||||
#endif
|
||||
|
||||
NanoleafNewDeviceDialog::NanoleafNewDeviceDialog(QWidget *parent) :
|
||||
QDialog(parent), ui(new Ui::NanoleafNewDeviceDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
ui->devicePortEdit->setText("16021");
|
||||
}
|
||||
|
||||
NanoleafNewDeviceDialog::~NanoleafNewDeviceDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void NanoleafNewDeviceDialog::changeEvent(QEvent *event)
|
||||
{
|
||||
if(event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
}
|
||||
|
||||
NanoleafDevice NanoleafNewDeviceDialog::show()
|
||||
{
|
||||
NanoleafDevice return_device;
|
||||
|
||||
int result = this->exec();
|
||||
|
||||
if(result != QDialog::Rejected)
|
||||
{
|
||||
return_device.ip = ui->deviceIPEdit->text().toStdString();
|
||||
return_device.port = ui->devicePortEdit->text().toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
return_device.ip = "";
|
||||
return_device.port = 0;
|
||||
}
|
||||
|
||||
return(return_device);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafNewDeviceDialog.h |
|
||||
| |
|
||||
| User interface for OpenRGB Nanoleaf dialog |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
struct NanoleafDevice
|
||||
{
|
||||
std::string ip;
|
||||
unsigned int port;
|
||||
};
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class NanoleafNewDeviceDialog;
|
||||
}
|
||||
|
||||
class NanoleafNewDeviceDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NanoleafNewDeviceDialog(QWidget *parent = nullptr);
|
||||
~NanoleafNewDeviceDialog();
|
||||
|
||||
NanoleafDevice show();
|
||||
|
||||
private:
|
||||
Ui::NanoleafNewDeviceDialog *ui;
|
||||
|
||||
private slots:
|
||||
void changeEvent(QEvent *event);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NanoleafNewDeviceDialog</class>
|
||||
<widget class="QDialog" name="NanoleafNewDeviceDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>184</width>
|
||||
<height>186</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>New Nanoleaf device</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="deviceIPLabel">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="deviceIPEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="devicePortLabel">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="devicePortEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>NanoleafNewDeviceDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>NanoleafNewDeviceDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafScanDialog.cpp |
|
||||
| |
|
||||
| User interface for Nanoleaf scan & pairing page |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "NanoleafScanDialog.h"
|
||||
#include "ui_NanoleafScanDialog.h"
|
||||
|
||||
#include "NanoleafNewDeviceDialog.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "LogManager.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
NanoleafScanDialog::NanoleafScanDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::NanoleafScanDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
on_NanoleafDeviceList_itemSelectionChanged(); // Refresh button state
|
||||
}
|
||||
|
||||
NanoleafScanDialog::~NanoleafScanDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::changeEvent(QEvent *event)
|
||||
{
|
||||
if(event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::on_AddNanoleafDeviceButton_clicked()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Open a popup to manually add a device by setting ip |
|
||||
| and port |
|
||||
\*-----------------------------------------------------*/
|
||||
NanoleafNewDeviceDialog dialog;
|
||||
NanoleafDevice device = dialog.show();
|
||||
if(!device.ip.empty())
|
||||
{
|
||||
LOG_TRACE("[%s] Add %s:%d", "Nanoleaf", device.ip.c_str(), device.port);
|
||||
std::string location = device.ip+":"+std::to_string(device.port);
|
||||
|
||||
if(entries.find(location) == entries.end())
|
||||
{
|
||||
NanoleafSettingsEntry* entry = new NanoleafSettingsEntry(QString::fromUtf8(device.ip.c_str()), 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();
|
||||
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
nanoleaf_settings["devices"][location]["ip"] = device.ip;
|
||||
nanoleaf_settings["devices"][location]["port"] = device.port;
|
||||
ResourceManager::get()->GetSettingsManager()->SetSettings("NanoleafDevices", nanoleaf_settings);
|
||||
ResourceManager::get()->GetSettingsManager()->SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::on_RemoveNanoleafDeviceButton_clicked()
|
||||
{
|
||||
/*-------------------------------------------------*\
|
||||
| Remove the selected device |
|
||||
\*-------------------------------------------------*/
|
||||
int cur_row = ui->NanoleafDeviceList->currentRow();
|
||||
|
||||
if(cur_row < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QListWidgetItem* item = ui->NanoleafDeviceList->item(cur_row);
|
||||
NanoleafSettingsEntry* entry = (NanoleafSettingsEntry*) ui->NanoleafDeviceList->itemWidget(item);
|
||||
|
||||
ui->NanoleafDeviceList->removeItemWidget(item);
|
||||
delete item;
|
||||
|
||||
std::string location = entry->getLocation();
|
||||
delete entries[location];
|
||||
entries.erase(location);
|
||||
|
||||
json nanoleaf_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("NanoleafDevices");
|
||||
nanoleaf_settings["devices"].erase(location);
|
||||
ResourceManager::get()->GetSettingsManager()->SetSettings("NanoleafDevices", nanoleaf_settings);
|
||||
ResourceManager::get()->GetSettingsManager()->SaveSettings();
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::on_ScanForNanoleafDevicesButton_clicked()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Create a worker thread for the mDNS query and hookup |
|
||||
| callbacks for when it finds devices |
|
||||
\*-----------------------------------------------------*/
|
||||
NanoleafScanningThread *scanThread = new NanoleafScanningThread;
|
||||
|
||||
connect(scanThread, SIGNAL(DeviceFound(QString, int)),
|
||||
SLOT(on_DeviceFound(QString, int)));
|
||||
|
||||
connect(scanThread, SIGNAL(finished()),
|
||||
scanThread, SLOT(deleteLater()));
|
||||
|
||||
scanThread->start();
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::on_DeviceFound(QString address, int port)
|
||||
{
|
||||
std::string location = address.toStdString()+":"+std::to_string(port);
|
||||
|
||||
if(entries.find(location) == entries.end())
|
||||
{
|
||||
NanoleafSettingsEntry* entry = new NanoleafSettingsEntry(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();
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafScanDialog::on_NanoleafDeviceList_itemSelectionChanged()
|
||||
{
|
||||
int cur_row = ui->NanoleafDeviceList->currentRow();
|
||||
|
||||
bool anySelected = (cur_row >= 0);
|
||||
ui->RemoveNanoleafDeviceButton->setEnabled(anySelected);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafScanDialog.h |
|
||||
| |
|
||||
| User interface for OpenRGB Nanoleaf scan & pairing page |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include "NanoleafSettingsEntry.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class NanoleafScanDialog;
|
||||
}
|
||||
|
||||
class NanoleafScanDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NanoleafScanDialog(QWidget *parent = nullptr);
|
||||
~NanoleafScanDialog();
|
||||
|
||||
private slots:
|
||||
void changeEvent(QEvent *event);
|
||||
void on_AddNanoleafDeviceButton_clicked();
|
||||
void on_RemoveNanoleafDeviceButton_clicked();
|
||||
void on_ScanForNanoleafDevicesButton_clicked();
|
||||
void on_DeviceFound(QString address, int port);
|
||||
|
||||
void on_NanoleafDeviceList_itemSelectionChanged();
|
||||
|
||||
private:
|
||||
Ui::NanoleafScanDialog *ui;
|
||||
std::map<std::string, NanoleafSettingsEntry*> entries;
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NanoleafScanDialog</class>
|
||||
<widget class="QDialog" name="NanoleafScanDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Nanoleaf Scan Page</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<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, a new entry should appear in the list below, then click the "Pair" button on the entry within 30 seconds.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="NanoleafDeviceList">
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="ScanForNanoleafDevicesButton">
|
||||
<property name="text">
|
||||
<string>Scan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="AddNanoleafDeviceButton">
|
||||
<property name="text">
|
||||
<string>Add manually</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="RemoveNanoleafDeviceButton">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,477 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafScanningThread.cpp |
|
||||
| |
|
||||
| OpenRGB Nanoleaf scanning thread |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _CRT_SECURE_NO_WARNINGS 1
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
#include "mdns.h"
|
||||
|
||||
#include "NanoleafScanningThread.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];
|
||||
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];
|
||||
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];
|
||||
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];
|
||||
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));
|
||||
|
||||
(static_cast<NanoleafScanningThread*>(user_data))->EmitDeviceFound(addrstr.str, address.sin_port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NanoleafScanningThread::EmitDeviceFound(QString address, int port)
|
||||
{
|
||||
emit DeviceFound(address, port);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Send a mDNS query |
|
||||
\*-----------------------------------------------------*/
|
||||
int NanoleafScanningThread::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;
|
||||
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 NanoleafScanningThread::run()
|
||||
{
|
||||
SendMDNSQuery();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafScanningThread.h |
|
||||
| |
|
||||
| OpenRGB Nanoleaf scanning thread |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class NanoleafScanningThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
void run();
|
||||
|
||||
int SendMDNSQuery();
|
||||
|
||||
signals:
|
||||
void DeviceFound(QString address, int port);
|
||||
|
||||
public:
|
||||
void EmitDeviceFound(QString address, int port);
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafSettingsEntry.cpp |
|
||||
| |
|
||||
| User interface for OpenRGB Nanoleaf settings entry |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "NanoleafSettingsEntry.h"
|
||||
#include "ui_NanoleafSettingsEntry.h"
|
||||
|
||||
#include "NanoleafScanDialog.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "NanoleafController.h"
|
||||
|
||||
NanoleafSettingsEntry::NanoleafSettingsEntry(QWidget *parent) :
|
||||
BaseManualDeviceEntry(parent),
|
||||
ui(new Ui::NanoleafSettingsEntry),
|
||||
paired(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
NanoleafSettingsEntry::NanoleafSettingsEntry(QString a_address, int a_port) :
|
||||
NanoleafSettingsEntry(nullptr)
|
||||
{
|
||||
address = a_address;
|
||||
port = a_port;
|
||||
const std::string location = getLocation();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
NanoleafSettingsEntry::~NanoleafSettingsEntry()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void NanoleafSettingsEntry::changeEvent(QEvent *event)
|
||||
{
|
||||
if(event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
}
|
||||
|
||||
void NanoleafSettingsEntry::on_PairButton_clicked()
|
||||
{
|
||||
try
|
||||
{
|
||||
auth_token = NanoleafController::Pair(address.toStdString(), port);
|
||||
|
||||
// Save auth token.
|
||||
std::string location = getLocation();
|
||||
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 NanoleafSettingsEntry::on_UnpairButton_clicked()
|
||||
{
|
||||
NanoleafController::Unpair(address.toStdString(), port, auth_token);
|
||||
|
||||
std::string location = getLocation();
|
||||
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();
|
||||
}
|
||||
|
||||
void NanoleafSettingsEntry::loadFromSettings(const json& data)
|
||||
{
|
||||
address = QString::fromStdString(data["ip"]);
|
||||
port = data["port"];
|
||||
|
||||
ui->IPValue->setText(address);
|
||||
ui->PortValue->setText(QString::fromStdString(std::to_string(port)));
|
||||
|
||||
if(data.contains("auth_token") && data["auth_token"].size())
|
||||
{
|
||||
auth_token = data["auth_token"];
|
||||
ui->AuthKeyValue->setText(QString::fromStdString(auth_token));
|
||||
ui->PairButton->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
auth_token.clear();
|
||||
ui->UnpairButton->hide();
|
||||
}
|
||||
}
|
||||
|
||||
json NanoleafSettingsEntry::saveSettings()
|
||||
{
|
||||
json result;
|
||||
|
||||
/*-------------------------------------------------*\
|
||||
| Required parameters |
|
||||
\*-------------------------------------------------*/
|
||||
result["ip"] = address.toStdString();
|
||||
result["port"] = port;
|
||||
if(!auth_token.empty())
|
||||
{
|
||||
result["auth_token"] = auth_token;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string NanoleafSettingsEntry::getLocation()
|
||||
{
|
||||
return (address.toStdString() + ":" + std::to_string(port));
|
||||
}
|
||||
|
||||
bool NanoleafSettingsEntry::isDataValid()
|
||||
{
|
||||
// stub
|
||||
return true;
|
||||
}
|
||||
|
||||
static BaseManualDeviceEntry* SpawnNanoleafSettingsEntry(const json& data)
|
||||
{
|
||||
if(data.empty())
|
||||
{
|
||||
// A special case: we open a new scanning dialog instead of returning a new entry
|
||||
// The caller should be able to handle this
|
||||
NanoleafScanDialog scanPage;
|
||||
scanPage.exec();
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
NanoleafSettingsEntry* entry = new NanoleafSettingsEntry;
|
||||
entry->loadFromSettings(data);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_MANUAL_DEVICE_TYPE("Nanoleaf", "NanoleafDevices", SpawnNanoleafSettingsEntry);
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| NanoleafSettingsEntry.h |
|
||||
| |
|
||||
| User interface for OpenRGB Nanoleaf settings entry |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseManualDeviceEntry.h"
|
||||
#include "NanoleafScanningThread.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class NanoleafSettingsEntry;
|
||||
}
|
||||
|
||||
class NanoleafSettingsEntry : public BaseManualDeviceEntry
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NanoleafSettingsEntry(QWidget *parent = nullptr);
|
||||
NanoleafSettingsEntry(QString a_address, int a_port);
|
||||
~NanoleafSettingsEntry();
|
||||
void loadFromSettings(const json& data);
|
||||
std::string getLocation();
|
||||
|
||||
json saveSettings() override;
|
||||
bool isDataValid() override;
|
||||
|
||||
private slots:
|
||||
void changeEvent(QEvent *event) override;
|
||||
void on_UnpairButton_clicked();
|
||||
void on_PairButton_clicked();
|
||||
|
||||
private:
|
||||
Ui::NanoleafSettingsEntry *ui;
|
||||
QString address;
|
||||
int port;
|
||||
std::string auth_token;
|
||||
bool paired;
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NanoleafSettingsEntry</class>
|
||||
<widget class="QWidget" name="NanoleafSettingsEntry">
|
||||
<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 notr="true">Nanoleaf Settings Entry</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Nanoleaf Device</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/>
|
||||
</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/>
|
||||
</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/>
|
||||
</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>
|
||||
Reference in New Issue
Block a user