diff --git a/Controllers/DDPController/DDPController.cpp b/Controllers/DDPController/DDPController.cpp new file mode 100644 index 00000000..e58f3c2e --- /dev/null +++ b/Controllers/DDPController/DDPController.cpp @@ -0,0 +1,314 @@ +/*---------------------------------------------------------*\ +| DDPController.cpp | +| | +| Driver for DDP protocol devices | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "DDPController.h" +#include "LogManager.h" +#include +#include + +DDPController::DDPController(const std::vector& device_list) +{ + devices = device_list; + unique_endpoints = NULL; + num_endpoints = 0; + sequence_number = 0; + keepalive_time_ms = 1000; // Default to 1 second keepalive as WLED times out quickly + keepalive_thread_run = false; + + InitializeNetPorts(); + + // Start keepalive thread + if(!devices.empty()) + { + keepalive_thread_run = true; + keepalive_thread = std::thread(&DDPController::KeepaliveThreadFunction, this); + } +} + +DDPController::~DDPController() +{ + // Stop keepalive thread + keepalive_thread_run = false; + if(keepalive_thread.joinable()) + { + keepalive_thread.join(); + } + + CloseNetPorts(); + if(unique_endpoints != NULL) + { + delete[] unique_endpoints; + } +} + +bool DDPController::InitializeNetPorts() +{ + if(devices.empty()) + { + return true; + } + + num_endpoints = 0; + + for(unsigned int dev_idx = 0; dev_idx < devices.size(); dev_idx++) + { + bool found = false; + for(unsigned int ep_idx = 0; ep_idx < num_endpoints; ep_idx++) + { + if(strcmp(unique_endpoints[ep_idx].ip, devices[dev_idx].ip.c_str()) == 0 && + unique_endpoints[ep_idx].port == devices[dev_idx].port) + { + found = true; + break; + } + } + if(!found) + { + num_endpoints++; + } + } + + unique_endpoints = new DDPEndpoint[num_endpoints]; + unsigned int endpoint_count = 0; + + for(unsigned int dev_idx = 0; dev_idx < devices.size(); dev_idx++) + { + bool found = false; + for(unsigned int ep_idx = 0; ep_idx < endpoint_count; ep_idx++) + { + if(strcmp(unique_endpoints[ep_idx].ip, devices[dev_idx].ip.c_str()) == 0 && + unique_endpoints[ep_idx].port == devices[dev_idx].port) + { + found = true; + break; + } + } + if(!found) + { + strncpy(unique_endpoints[endpoint_count].ip, devices[dev_idx].ip.c_str(), 15); + unique_endpoints[endpoint_count].ip[15] = '\0'; + unique_endpoints[endpoint_count].port = devices[dev_idx].port; + endpoint_count++; + } + } + + for(unsigned int ep_idx = 0; ep_idx < num_endpoints; ep_idx++) + { + net_port* port = new net_port(); + char port_str[16]; + snprintf(port_str, 16, "%d", unique_endpoints[ep_idx].port); + + if(port->udp_client(unique_endpoints[ep_idx].ip, port_str)) + { + udp_ports.push_back(port); + } + else + { + udp_ports.push_back(NULL); + } + } + + return true; +} + +void DDPController::CloseNetPorts() +{ + for(unsigned int port_idx = 0; port_idx < udp_ports.size(); port_idx++) + { + if(udp_ports[port_idx] != NULL) + { + delete udp_ports[port_idx]; + } + } + udp_ports.clear(); +} + +int DDPController::GetPortIndex(const DDPDevice& device) +{ + for(unsigned int ep_idx = 0; ep_idx < num_endpoints; ep_idx++) + { + if(strcmp(unique_endpoints[ep_idx].ip, device.ip.c_str()) == 0 && + unique_endpoints[ep_idx].port == device.port) + { + return (int)ep_idx; + } + } + return -1; +} + +void DDPController::UpdateLEDs(const std::vector& colors) +{ + if(udp_ports.empty()) return; + + // Store colors and time for keepalive + { + std::lock_guard lock(last_update_mutex); + last_colors = colors; + last_update_time = std::chrono::steady_clock::now(); + } + + unsigned int color_index = 0; + + for(unsigned int dev_idx = 0; dev_idx < devices.size(); dev_idx++) + { + if(color_index >= colors.size()) break; + + unsigned int bytes_per_pixel = 3; + unsigned int total_bytes = devices[dev_idx].num_leds * bytes_per_pixel; + std::vector device_data(total_bytes); + + for(unsigned int led_idx = 0; led_idx < devices[dev_idx].num_leds && (color_index + led_idx) < colors.size(); led_idx++) + { + unsigned int color = colors[color_index + led_idx]; + unsigned char r = color & 0xFF; + unsigned char g = (color >> 8) & 0xFF; + unsigned char b = (color >> 16) & 0xFF; + unsigned int pixel_offset = led_idx * bytes_per_pixel; + + device_data[pixel_offset + 0] = r; + device_data[pixel_offset + 1] = g; + device_data[pixel_offset + 2] = b; + } + + unsigned int max_data_per_packet = DDP_MAX_PACKET_SIZE - DDP_HEADER_SIZE; + unsigned int bytes_sent = 0; + + while(bytes_sent < total_bytes) + { + unsigned int chunk_size = (max_data_per_packet < (total_bytes - bytes_sent)) ? max_data_per_packet : (total_bytes - bytes_sent); + + if(!SendDDPPacket(devices[dev_idx], device_data.data() + bytes_sent, (unsigned short)chunk_size, bytes_sent)) + break; + + bytes_sent += chunk_size; + } + + color_index += devices[dev_idx].num_leds; + } + + sequence_number++; +} + +bool DDPController::SendDDPPacket(const DDPDevice& device, const unsigned char* data, unsigned short length, unsigned int offset) +{ + int port_index = GetPortIndex(device); + if(port_index < 0 || port_index >= (int)udp_ports.size()) + { + return false; + } + + if(udp_ports[port_index] == NULL) + { + net_port* port = new net_port(); + char port_str[16]; + snprintf(port_str, 16, "%d", unique_endpoints[port_index].port); + + if(port->udp_client(unique_endpoints[port_index].ip, port_str)) + { + udp_ports[port_index] = port; + } + else + { + delete port; + return false; + } + } + + std::vector packet(DDP_HEADER_SIZE + length); + ddp_header* header = (ddp_header*)packet.data(); + + // CRITICAL FIX: Use 0x41 instead of 0x40 - WLED requires the Push bit to be set + header->flags = DDP_FLAG_VER_1 | DDP_FLAG_PUSH; + header->sequence = sequence_number; + header->data_type = 1; // RGB data type + header->dest_id = 1; // Default output device + header->data_offset = htonl(offset); + header->data_length = htons(length); + + memcpy(packet.data() + DDP_HEADER_SIZE, data, length); + + int bytes_sent = udp_ports[port_index]->udp_write((char*)packet.data(), (int)packet.size()); + + return bytes_sent == (int)packet.size(); +} + +void DDPController::SetKeepaliveTime(unsigned int time_ms) +{ + keepalive_time_ms = time_ms; +} + +void DDPController::KeepaliveThreadFunction() +{ + while(keepalive_thread_run) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Check every 100ms + + if(keepalive_time_ms == 0) // Keepalive disabled + continue; + + std::vector colors_to_send; + bool should_send = false; + + { + std::lock_guard lock(last_update_mutex); + auto now = std::chrono::steady_clock::now(); + auto time_since_update = std::chrono::duration_cast(now - last_update_time).count(); + + if(time_since_update >= keepalive_time_ms && !last_colors.empty()) + { + colors_to_send = last_colors; + should_send = true; + last_update_time = now; // Reset timer + } + } + + if(should_send) + { + // Send keepalive packet with last known colors + unsigned int color_index = 0; + + for(unsigned int dev_idx = 0; dev_idx < devices.size(); dev_idx++) + { + if(color_index >= colors_to_send.size()) break; + + unsigned int bytes_per_pixel = 3; + unsigned int total_bytes = devices[dev_idx].num_leds * bytes_per_pixel; + std::vector device_data(total_bytes); + + for(unsigned int led_idx = 0; led_idx < devices[dev_idx].num_leds && (color_index + led_idx) < colors_to_send.size(); led_idx++) + { + unsigned int color = colors_to_send[color_index + led_idx]; + unsigned char r = color & 0xFF; + unsigned char g = (color >> 8) & 0xFF; + unsigned char b = (color >> 16) & 0xFF; + unsigned int pixel_offset = led_idx * bytes_per_pixel; + + device_data[pixel_offset + 0] = r; + device_data[pixel_offset + 1] = g; + device_data[pixel_offset + 2] = b; + } + + unsigned int max_data_per_packet = DDP_MAX_PACKET_SIZE - DDP_HEADER_SIZE; + unsigned int bytes_sent = 0; + + while(bytes_sent < total_bytes) + { + unsigned int chunk_size = (max_data_per_packet < (total_bytes - bytes_sent)) ? max_data_per_packet : (total_bytes - bytes_sent); + + if(!SendDDPPacket(devices[dev_idx], device_data.data() + bytes_sent, (unsigned short)chunk_size, bytes_sent)) + break; + + bytes_sent += chunk_size; + } + + color_index += devices[dev_idx].num_leds; + } + } + } +} diff --git a/Controllers/DDPController/DDPController.h b/Controllers/DDPController/DDPController.h new file mode 100644 index 00000000..3d86e264 --- /dev/null +++ b/Controllers/DDPController/DDPController.h @@ -0,0 +1,93 @@ +/*---------------------------------------------------------*\ +| DDPController.h | +| | +| Driver for DDP protocol devices | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "net_port.h" + +#define DDP_DEFAULT_PORT 4048 +#define DDP_HEADER_SIZE 10 +#define DDP_HEADER_SIZE_TC 14 +#define DDP_VERSION 1 +#define DDP_MAX_PACKET_SIZE 1480 + +#define DDP_FLAG_VER_MASK 0xC0 +#define DDP_FLAG_VER_1 0x40 +#define DDP_FLAG_TIMECODE 0x10 +#define DDP_FLAG_STORAGE 0x08 +#define DDP_FLAG_REPLY 0x04 +#define DDP_FLAG_QUERY 0x02 +#define DDP_FLAG_PUSH 0x01 + +#pragma pack(push, 1) +struct ddp_header +{ + unsigned char flags; + unsigned char sequence; + unsigned char data_type; + unsigned char dest_id; + unsigned int data_offset; + unsigned short data_length; +}; +#pragma pack(pop) + +struct DDPDevice +{ + std::string name; + std::string ip; + unsigned short port; + unsigned int num_leds; +}; + +struct DDPEndpoint +{ + char ip[16]; + unsigned short port; +}; + +class DDPController +{ +public: + DDPController(const std::vector& devices); + ~DDPController(); + + void UpdateLEDs(const std::vector& colors); + void SetKeepaliveTime(unsigned int time_ms); + +private: + std::vector devices; + std::vector udp_ports; + DDPEndpoint* unique_endpoints; + unsigned int num_endpoints; + unsigned char sequence_number; + + // Keepalive functionality + std::atomic keepalive_thread_run; + std::thread keepalive_thread; + std::mutex last_update_mutex; + std::chrono::steady_clock::time_point last_update_time; + std::vector last_colors; + unsigned int keepalive_time_ms; + + bool InitializeNetPorts(); + void CloseNetPorts(); + int GetPortIndex(const DDPDevice& device); + bool SendDDPPacket(const DDPDevice& device, + const unsigned char* data, + unsigned short length, + unsigned int offset = 0); + void KeepaliveThreadFunction(); +}; diff --git a/Controllers/DDPController/DDPControllerDetect.cpp b/Controllers/DDPController/DDPControllerDetect.cpp new file mode 100644 index 00000000..61eb506e --- /dev/null +++ b/Controllers/DDPController/DDPControllerDetect.cpp @@ -0,0 +1,100 @@ +/*---------------------------------------------------------*\ +| DDPControllerDetect.cpp | +| | +| Detector for DDP devices | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include +#include +#include "Detector.h" +#include "RGBController.h" +#include "RGBController_DDP.h" +#include "SettingsManager.h" +#include "LogManager.h" +#include "nlohmann/json.hpp" + +using json = nlohmann::json; + +void DetectDDPControllers() +{ + json ddp_settings; + std::vector> device_lists; + DDPDevice dev; + unsigned int keepalive_time = 1000; // Default 1 second + + ddp_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("DDPDevices"); + + // Read keepalive setting + if(ddp_settings.contains("keepalive_time")) + { + keepalive_time = ddp_settings["keepalive_time"]; + } + + if(ddp_settings.contains("devices")) + { + for(unsigned int device_idx = 0; device_idx < ddp_settings["devices"].size(); device_idx++) + { + dev.name = ""; + dev.ip = ""; + dev.port = DDP_DEFAULT_PORT; + dev.num_leds = 0; + + if(ddp_settings["devices"][device_idx].contains("name")) + dev.name = ddp_settings["devices"][device_idx]["name"]; + if(ddp_settings["devices"][device_idx].contains("ip")) + dev.ip = ddp_settings["devices"][device_idx]["ip"]; + if(ddp_settings["devices"][device_idx].contains("port")) + dev.port = ddp_settings["devices"][device_idx]["port"]; + if(ddp_settings["devices"][device_idx].contains("num_leds")) + dev.num_leds = ddp_settings["devices"][device_idx]["num_leds"]; + + if(dev.name.empty()) + dev.name = "DDP Device " + std::to_string(device_idx + 1); + if(dev.ip.empty()) + { + continue; + } + if(dev.num_leds == 0) + { + continue; + } + + bool device_added_to_existing_list = false; + + for(unsigned int list_idx = 0; list_idx < device_lists.size(); list_idx++) + { + for(unsigned int existing_device_idx = 0; existing_device_idx < device_lists[list_idx].size(); existing_device_idx++) + { + if(dev.ip == device_lists[list_idx][existing_device_idx].ip && + dev.port == device_lists[list_idx][existing_device_idx].port) + { + device_lists[list_idx].push_back(dev); + device_added_to_existing_list = true; + break; + } + } + if(device_added_to_existing_list) + break; + } + + if(!device_added_to_existing_list) + { + std::vector new_list; + new_list.push_back(dev); + device_lists.push_back(new_list); + } + } + + for(unsigned int list_idx = 0; list_idx < device_lists.size(); list_idx++) + { + RGBController_DDP* rgb_controller = new RGBController_DDP(device_lists[list_idx]); + rgb_controller->SetKeepaliveTime(keepalive_time); + ResourceManager::get()->RegisterRGBController(rgb_controller); + } + } +} + +REGISTER_DETECTOR("DDP", DetectDDPControllers); diff --git a/Controllers/DDPController/RGBController_DDP.cpp b/Controllers/DDPController/RGBController_DDP.cpp new file mode 100644 index 00000000..9ba2a453 --- /dev/null +++ b/Controllers/DDPController/RGBController_DDP.cpp @@ -0,0 +1,136 @@ +/*---------------------------------------------------------*\ +| RGBController_DDP.cpp | +| | +| RGBController for DDP devices | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "RGBController_DDP.h" + +/**------------------------------------------------------------------*\ + @name DDP Devices + @category LEDStrip + @type Network + @save :x: + @direct :white_check_mark: + @effects :x: + @detectors DetectDDPControllers + @comment +\*-------------------------------------------------------------------*/ + +RGBController_DDP::RGBController_DDP(std::vector device_list) +{ + devices = device_list; + name = "DDP Device Group"; + type = DEVICE_TYPE_LEDSTRIP; + description = "Distributed Display Protocol Device"; + location = "DDP: "; + + if(devices.size() == 1) + name = devices[0].name; + else if(!devices[0].ip.empty()) + name += " (" + devices[0].ip + ")"; + + if(!devices[0].ip.empty()) + location += devices[0].ip + ":" + std::to_string(devices[0].port); + + mode Direct; + Direct.name = "Direct"; + Direct.value = 0; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS; + Direct.color_mode = MODE_COLORS_PER_LED; + Direct.brightness_min = 0; + Direct.brightness_max = 100; + Direct.brightness = 100; + modes.push_back(Direct); + + controller = new DDPController(devices); + SetupZones(); +} + +RGBController_DDP::~RGBController_DDP() +{ + delete controller; +} + +void RGBController_DDP::SetupZones() +{ + for(unsigned int zone_idx = 0; zone_idx < devices.size(); zone_idx++) + { + zone led_zone; + led_zone.name = devices[zone_idx].name; + led_zone.type = ZONE_TYPE_LINEAR; + led_zone.leds_min = devices[zone_idx].num_leds; + led_zone.leds_max = devices[zone_idx].num_leds; + led_zone.leds_count = devices[zone_idx].num_leds; + led_zone.matrix_map = NULL; + zones.push_back(led_zone); + } + + for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + for(unsigned int led_idx = 0; led_idx < zones[zone_idx].leds_count; led_idx++) + { + led new_led; + new_led.name = zones[zone_idx].name + " LED " + std::to_string(led_idx + 1); + new_led.value = 0; + leds.push_back(new_led); + } + } + SetupColors(); +} + +void RGBController_DDP::ResizeZone(int /*zone*/, int /*new_size*/) +{ +} + +void RGBController_DDP::DeviceUpdateLEDs() +{ + std::vector brightness_adjusted_colors; + brightness_adjusted_colors.reserve(colors.size()); + float brightness_scale = (float)modes[active_mode].brightness / 100.0f; + + for(unsigned int color_idx = 0; color_idx < colors.size(); color_idx++) + { + unsigned int color = colors[color_idx]; + unsigned char r = color & 0xFF; + unsigned char g = (color >> 8) & 0xFF; + unsigned char b = (color >> 16) & 0xFF; + r = (unsigned char)(r * brightness_scale); + g = (unsigned char)(g * brightness_scale); + b = (unsigned char)(b * brightness_scale); + unsigned int adjusted_color = r | (g << 8) | (b << 16); + brightness_adjusted_colors.push_back(adjusted_color); + } + + controller->UpdateLEDs(brightness_adjusted_colors); +} + +void RGBController_DDP::UpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_DDP::UpdateSingleLED(int /*led*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_DDP::DeviceUpdateMode() +{ +} + +void RGBController_DDP::SetCustomMode() +{ + active_mode = 0; +} + +void RGBController_DDP::SetKeepaliveTime(unsigned int time_ms) +{ + if(controller != nullptr) + { + controller->SetKeepaliveTime(time_ms); + } +} diff --git a/Controllers/DDPController/RGBController_DDP.h b/Controllers/DDPController/RGBController_DDP.h new file mode 100644 index 00000000..7738ba3c --- /dev/null +++ b/Controllers/DDPController/RGBController_DDP.h @@ -0,0 +1,34 @@ +/*---------------------------------------------------------*\ +| RGBController_DDP.h | +| | +| RGBController for DDP devices | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include "RGBController.h" +#include "DDPController.h" + +class RGBController_DDP : public RGBController +{ +public: + RGBController_DDP(std::vector device_list); + ~RGBController_DDP(); + + void SetupZones(); + void ResizeZone(int zone, int new_size); + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + void DeviceUpdateMode(); + void SetCustomMode(); + void SetKeepaliveTime(unsigned int time_ms); + +private: + std::vector devices; + DDPController* controller; +}; diff --git a/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.cpp b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.cpp new file mode 100644 index 00000000..ff078f2b --- /dev/null +++ b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.cpp @@ -0,0 +1,96 @@ +/*---------------------------------------------------------*\ +| DDPSettingsEntry.cpp | +| | +| User interface for OpenRGB DDP settings entry | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "DDPSettingsEntry.h" +#include "ui_DDPSettingsEntry.h" +#include "ManualDevicesTypeManager.h" + +DDPSettingsEntry::DDPSettingsEntry(QWidget *parent) : + BaseManualDeviceEntry(parent), + ui(new Ui::DDPSettingsEntry) +{ + ui->setupUi(this); +} + +DDPSettingsEntry::~DDPSettingsEntry() +{ + delete ui; +} + +void DDPSettingsEntry::changeEvent(QEvent *event) +{ + if(event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } +} + +void DDPSettingsEntry::loadFromSettings(const json& data) +{ + if(data.contains("name")) + { + ui->NameEdit->setText(QString::fromStdString(data["name"])); + } + + if(data.contains("ip")) + { + ui->IPEdit->setText(QString::fromStdString(data["ip"])); + } + + if(data.contains("port")) + { + ui->PortSpinBox->setValue(data["port"]); + } + else + { + ui->PortSpinBox->setValue(4048); // DDP default port + } + + if(data.contains("num_leds")) + { + ui->NumLedsSpinBox->setValue(data["num_leds"]); + } + + if(data.contains("keepalive_time")) + { + ui->KeepaliveSpinBox->setValue(data["keepalive_time"]); + } + else + { + ui->KeepaliveSpinBox->setValue(1000); // Default 1 second + } +} + +json DDPSettingsEntry::saveSettings() +{ + json result; + + result["name"] = ui->NameEdit->text().toStdString(); + result["ip"] = ui->IPEdit->text().toStdString(); + result["port"] = ui->PortSpinBox->value(); + result["num_leds"] = ui->NumLedsSpinBox->value(); + result["keepalive_time"] = ui->KeepaliveSpinBox->value(); + + return result; +} + +bool DDPSettingsEntry::isDataValid() +{ + // Check if IP is not empty and num_leds > 0 + return !ui->IPEdit->text().isEmpty() && ui->NumLedsSpinBox->value() > 0; +} + +static BaseManualDeviceEntry* SpawnDDPEntry(const json& data) +{ + DDPSettingsEntry* entry = new DDPSettingsEntry; + entry->loadFromSettings(data); + return entry; +} + +REGISTER_MANUAL_DEVICE_TYPE("DDP (Distributed Display Protocol)", "DDPDevices", SpawnDDPEntry); diff --git a/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.h b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.h new file mode 100644 index 00000000..7de58a03 --- /dev/null +++ b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.h @@ -0,0 +1,35 @@ +/*---------------------------------------------------------*\ +| DDPSettingsEntry.h | +| | +| User interface for OpenRGB DDP settings entry | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include "BaseManualDeviceEntry.h" + +namespace Ui +{ + class DDPSettingsEntry; +} + +class DDPSettingsEntry : public BaseManualDeviceEntry +{ + Q_OBJECT + +public: + explicit DDPSettingsEntry(QWidget *parent = nullptr); + ~DDPSettingsEntry(); + void loadFromSettings(const json& data); + json saveSettings() override; + bool isDataValid() override; + +private: + Ui::DDPSettingsEntry *ui; + +private slots: + void changeEvent(QEvent *event) override; +}; diff --git a/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.ui b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.ui new file mode 100644 index 00000000..f89be5bb --- /dev/null +++ b/qt/ManualDevicesSettingsPage/DDPSettingsEntry/DDPSettingsEntry.ui @@ -0,0 +1,130 @@ + + + DDPSettingsEntry + + + + 0 + 0 + 400 + 200 + + + + + 0 + 0 + + + + DDP Settings Entry + + + + + + DDP Device + + + + + + Name: + + + + + + + Device Name + + + + + + + IP Address: + + + + + + + 192.168.1.100 + + + + + + + Port: + + + + + + + 1 + + + 65535 + + + 4048 + + + + + + + Number of LEDs: + + + + + + + 1 + + + 10000 + + + 50 + + + + + + + Keepalive Time (ms): + + + + + + + 0 + + + 60000 + + + 1000 + + + ms + + + Disabled + + + + + + + + + + +