diff --git a/README.md b/README.md index 1d97d9709..d8034944d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa ![SoundViz](screenshots/soundviz_win.png) -* Mood Lamps +* Scriptable Mood Lamps ([see here how](/Software/res/moodlamps/)) ![Mood Lamps](screenshots/moodlamps_win.png) @@ -104,6 +104,7 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa You will need the following packages, usually all of them are in distro's repository: * qt5-default * libqt5serialport5-dev +* qtdeclarative5-dev * build-essential * libgtk2.0-dev * libusb-1.0-0-dev diff --git a/Software/res/LightpackResources.qrc b/Software/res/LightpackResources.qrc index 5978ad618..5fe13f260 100644 --- a/Software/res/LightpackResources.qrc +++ b/Software/res/LightpackResources.qrc @@ -39,5 +39,12 @@ icons/Prismatik-pixmap.png text/cast.html icons/persist.png + + moodlamps/static.mjs + moodlamps/rgb_is_life.mjs + moodlamps/rgb_cycle.mjs + moodlamps/breathing.mjs + moodlamps/fire.mjs + moodlamps/random_noise.mjs diff --git a/Software/res/moodlamps/README.md b/Software/res/moodlamps/README.md new file mode 100644 index 000000000..b9cadb756 --- /dev/null +++ b/Software/res/moodlamps/README.md @@ -0,0 +1,144 @@ +Moodlamp scripting +--------- +Prismatik uses [QJSEngine](https://doc.qt.io/qt-5/qjsengine.html) for parsing and running moodlamp scripts, so if you are familiar with JavaScript you'll be fine. + +The scripts live in your user's Prismatik folder: +- Windows: `C:\Users\\Prismatik\moodlamps` +- macOS & Linux: `~/.Prismatik/moodlamps` + +File name should obey these rules: +- allowed characters `a-z`, `0-9` and `_` (underscore) +- `.mjs` extension +- unique name + +For ex: `my_cool_lamp.mjs` + + +**The bare minimum template:** +```js +export const name = "your lamp name here" +export const interval = 50 // frame time in ms + +/** + * @param {integer} baseColor integer RGB value of the base color that is selected in Prismatik + * @param {array} colors array of RGB integers representing your LEDs + * @return {array} array of new RGB integers representing your LEDs + */ +export function tick(baseColor, colors) +{ + for (let i = 0; i < colors.length; i++) + { + // your code here, for ex: + colors[i] = baseColor // will set all LEDs to the color you set in Prismatik + } + + return colors +} +``` + + +**More examples:** + +```js +export const name = "your lamp name here" +export const interval = 50 // frame time in ms + +// if you need a global constant +const myBlueConst = 0x0000FF // blue color + +// if you need a variable that persists between frames (a counter for ex), declare it here +let someVariable = 0 + +export function tick(baseColor, colors) +{ + for (let i = 0; i < colors.length; i++) + { + // to set all your LEDs to white + colors[i] = 0xFFFFFF + // or to blue + colors[i] = myBlueConst + + // random colors for each LED + colors[i] = Math.round(Math.random() * 0xFFFFFF) + } + + someVariable++ // counter from above + + return colors +} + +``` + +**Recommended way:** + +For convenience, Prismatik partially exposes `QColor` class, see [here for documentation](https://doc.qt.io/qt-5/qcolor.html) and [here for exposed methods](/Software/src/QColorMetaWrapper.hpp). +Prismatik uses `QColor` internally so for best compatibility it's better to use this class. +Raw RGB values should work, but use them at your own risk. + +Example from `rgb_is_life.js`: +```js +export const name = "RGB is Life" +export const interval = 33 // ms + +const speed = 1.5 + +let m_frames = 0 + +export function tick(baseColor, colors) +{ + // transform base RGB value into QColor + baseColor = new QColor(baseColor) + + // RGB transition math + const degrees = 360.0 / colors.length + const step = speed * m_frames++ + for (let i = 0; i < colors.length; i++) + { + // transform current color for a given LED into QColor + let color = new QColor(colors[i]) + + // increment baseColor's hue value for that smooth RGB rotation + const hue = baseColor.hslHue() + degrees * i + step + + // set new hue while preserving baseColor's saturation and lightness + color.setHsl(hue, baseColor.hslSaturation(), baseColor.lightness()) + + // assign the new color + // colors[] has to contain .rgb() values + colors[i] = color.rgb() + } + if (step > 360) + m_frames = 0 + + return colors +} +``` + + +To see your changes use the reload button or relaunch the app. + + +**Warnig:** scripts bundled with Prismatik are overwritten on launch, if you want to customize Prismatik's scripts, make sure to make a copy of them and modify those. + + +**Logging and console** + +If you enable logs in Prismatik your JS errors will be logged to +- Windows: `C:\Users\\Prismatik\Logs` +- macOS & Linux: `~/.Prismatik/Logs` + + +You can also enable the [Console API](https://doc.qt.io/qt-5/qtquick-debugging.html#console-api) + +```js +export const enableConsole = true +... +export function tick() +{ + ... + console.log("some debug") + ... +} +``` + +Don't forget to remove when you are done diff --git a/Software/res/moodlamps/breathing.mjs b/Software/res/moodlamps/breathing.mjs new file mode 100644 index 000000000..6cc54f8e4 --- /dev/null +++ b/Software/res/moodlamps/breathing.mjs @@ -0,0 +1,22 @@ +export const name = "Breathing" +export const interval = 40 // ms + +let lightnessInc = -1 +let lightness = -1 + +export function tick(baseColor, colors) +{ + baseColor = new QColor(baseColor) + if (lightness == -1) + lightness = baseColor.lightness() + let color = new QColor() + if (lightness <= baseColor.lightness() / 2 && lightnessInc == -1) + lightnessInc = 1 + else if (lightness >= baseColor.lightness() - 1 && lightnessInc == 1) + lightnessInc = -1 + + lightness += lightnessInc + + color.setHsl(baseColor.hslHue(), baseColor.hslSaturation(), lightness) + return colors.fill(color.rgb()) +} diff --git a/Software/res/moodlamps/fire.mjs b/Software/res/moodlamps/fire.mjs new file mode 100644 index 000000000..1fc02f998 --- /dev/null +++ b/Software/res/moodlamps/fire.mjs @@ -0,0 +1,74 @@ +export const name = "Fire" +export const interval = 33 // ms + +let m_lightness = [] +let m_center = 0 + +const Cooling = 8 +const SparkMax = 160 +const SparkMin = 100 +const DefaultLightness = 127 + +function randomBounded(max) { + return Math.floor(Math.random() * Math.floor(max)) +} + +export function tick(baseColor, colors) +{ + if (colors.length < 2) + return colors + + if (colors.length > m_lightness.length) { + for (let i = m_lightness.length; i < colors.length; ++i) + m_lightness.push(DefaultLightness) + } + + // heavily inspired by FastLED Fire2012 demo + // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino + + const centerMax = Math.round(colors.length / 4) + const middleLed = Math.floor(colors.length / 2) + const sparkCount = Math.round(colors.length / 12) + + m_center += randomBounded(2) ? -1 : 1 + m_center = Math.max(-centerMax, Math.min(centerMax, m_center)) + + for (let i = 0; i < middleLed + m_center; ++i) { + const minLightnessReduction = Cooling * Math.pow(i / (middleLed + m_center), 3) + const maxLightnessReduction = minLightnessReduction * 2 // useless? + const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3 + m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256 + } + + for (let i = colors.length - 1; i >= middleLed + m_center; --i) { + const minLightnessReduction = Cooling * Math.pow((colors.length - 1 - i) / (middleLed - m_center), 3) + const maxLightnessReduction = minLightnessReduction * 2 // useless? + const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3 + m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256 + } + + for (let k = middleLed + m_center; k > 1; --k) + m_lightness[k] = Math.round((m_lightness[k - 1] + m_lightness[k - 2] * 2) / 3) % 256 + + for (let k = middleLed + m_center; k < colors.length - 2; ++k) + m_lightness[k] = Math.round((m_lightness[k + 1] + m_lightness[k + 2] * 2) / 3) % 256 + + + if (randomBounded(2) == 0) { + const y = randomBounded(Math.max(1, sparkCount)) + m_lightness[y] = Math.max(SparkMax, m_lightness[y] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256 + } + if (randomBounded(2) == 0) { + const z = colors.length - 1 - randomBounded(Math.max(1, sparkCount)) + m_lightness[z] = Math.max(SparkMax, m_lightness[z] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256 + } + + let color = new QColor(baseColor) + for (let i = 0; i < colors.length; i++) + { + color.setHsl(color.hslHue(), color.hslSaturation(), m_lightness[i]) + colors[i] = color.rgb() + } + + return colors +} diff --git a/Software/res/moodlamps/random_noise.mjs b/Software/res/moodlamps/random_noise.mjs new file mode 100644 index 000000000..362277b76 --- /dev/null +++ b/Software/res/moodlamps/random_noise.mjs @@ -0,0 +1,12 @@ +export const name = "Random noise" +export const interval = 50 // ms + +export function tick(baseColor, colors) +{ + let color = new QColor(baseColor) + for (let i = 0; i < colors.length; i++) { + color.setRgb(Math.random() * 0xFFFFFF) + colors[i] = color.rgb() + } + return colors +} diff --git a/Software/res/moodlamps/rgb_cycle.mjs b/Software/res/moodlamps/rgb_cycle.mjs new file mode 100644 index 000000000..159fbfd8d --- /dev/null +++ b/Software/res/moodlamps/rgb_cycle.mjs @@ -0,0 +1,15 @@ +export const name = "RGB Cycle" +export const interval = 33 // ms + +let degrees = 0 + +export function tick(baseColor, colors) +{ + baseColor = new QColor(baseColor) + baseColor.setHsl(baseColor.hslHue() + degrees++, baseColor.hslSaturation(), baseColor.lightness()) + + if (degrees > 360) + degrees = 0 + + return colors.fill(baseColor.rgb()) +} diff --git a/Software/res/moodlamps/rgb_is_life.mjs b/Software/res/moodlamps/rgb_is_life.mjs new file mode 100644 index 000000000..9e8ae8797 --- /dev/null +++ b/Software/res/moodlamps/rgb_is_life.mjs @@ -0,0 +1,22 @@ +export const name = "RGB is Life" +export const interval = 33 // ms +const speed = 1.5 + +let m_frames = 0 + +export default function tick(baseColor, colors) +{ + baseColor = new QColor(baseColor) + const degrees = 360.0 / colors.length + const step = speed * m_frames++ + for (let i = 0; i < colors.length; i++) + { + let color = new QColor(colors[i]) + color.setHsl(baseColor.hslHue() + degrees * i + step, baseColor.hslSaturation(), baseColor.lightness()) + colors[i] = color.rgb() + } + if (step > 360) + m_frames = 0 + + return colors +} diff --git a/Software/res/moodlamps/static.mjs b/Software/res/moodlamps/static.mjs new file mode 100644 index 000000000..f1dd4c00e --- /dev/null +++ b/Software/res/moodlamps/static.mjs @@ -0,0 +1,7 @@ +export const name = "Static (default)" +export const interval = 50 // ms + +export function tick(baseColor, colors) +{ + return colors.fill(baseColor) +} diff --git a/Software/src/LightpackApplication.cpp b/Software/src/LightpackApplication.cpp index 5523d2bb6..f778a6b32 100644 --- a/Software/src/LightpackApplication.cpp +++ b/Software/src/LightpackApplication.cpp @@ -699,8 +699,7 @@ void LightpackApplication::initGrabManager() DEBUG_LOW_LEVEL << Q_FUNC_INFO; m_grabManager = new GrabManager(NULL); - m_moodlampManager = new MoodLampManager(NULL); - m_moodlampManager->initFromSettings(); + m_moodlampManager = new MoodLampManager(m_applicationDirPath); #ifdef SOUNDVIZ_SUPPORT m_soundManager = SoundManagerBase::create(m_settingsWindow->winId(), NULL); @@ -727,7 +726,7 @@ void LightpackApplication::initGrabManager() connect(settings(), SIGNAL(moodLampColorChanged(QColor)), m_moodlampManager, SLOT(setCurrentColor(QColor))); connect(settings(), SIGNAL(moodLampSpeedChanged(int)), m_moodlampManager, SLOT(setLiquidModeSpeed(int))); connect(settings(), SIGNAL(moodLampLiquidModeChanged(bool)), m_moodlampManager, SLOT(setLiquidMode(bool))); - connect(settings(), SIGNAL(moodLampLampChanged(int)), m_moodlampManager, SLOT(setCurrentLamp(int))); + connect(settings(), SIGNAL(moodLampLampChanged(const QString&)), m_moodlampManager, SLOT(setCurrentLamp(const QString&))); connect(settings(), SIGNAL(sendDataOnlyIfColorsChangesChanged(bool)), m_moodlampManager, SLOT(setSendDataOnlyIfColorsChanged(bool))); #ifdef SOUNDVIZ_SUPPORT @@ -774,8 +773,9 @@ void LightpackApplication::initGrabManager() } #endif - connect(m_settingsWindow, SIGNAL(requestMoodLampLamps()), m_moodlampManager, SLOT(requestLampList())); - connect(m_moodlampManager, SIGNAL(lampList(const QList &, int)), m_settingsWindow, SLOT(updateAvailableMoodLampLamps(const QList &, int))); + connect(m_settingsWindow, SIGNAL(requestMoodLampLamps(bool)), m_moodlampManager, SLOT(requestLampList(bool))); + connect(m_moodlampManager, SIGNAL(lampList(const QList &)), m_settingsWindow, SLOT(updateAvailableMoodLampLamps(const QList &))); + connect(m_settingsWindow, SIGNAL(openMoodLampScriptDir()), this, SLOT(openMoodLampScriptDir())); } connect(m_grabManager, SIGNAL(ambilightTimeOfUpdatingColors(double)), m_pluginInterface, SLOT(refreshAmbilightEvaluated(double))); @@ -793,6 +793,11 @@ void LightpackApplication::initGrabManager() #endif } +void LightpackApplication::openMoodLampScriptDir() +{ + QDesktopServices::openUrl(QUrl(m_moodlampManager->scriptDir())); +} + void LightpackApplication::commitData(QSessionManager &sessionManager) { Q_UNUSED(sessionManager); diff --git a/Software/src/LightpackApplication.hpp b/Software/src/LightpackApplication.hpp index 423546cea..9a1172dbf 100644 --- a/Software/src/LightpackApplication.hpp +++ b/Software/src/LightpackApplication.hpp @@ -83,6 +83,7 @@ private slots: void onFocusChanged(QWidget *, QWidget *); void quitFromWizard(int result); void onSessionChange(int change); + void openMoodLampScriptDir(); private: void processCommandLineArguments(); diff --git a/Software/src/LiquidColorGenerator.cpp b/Software/src/LiquidColorGenerator.cpp index 76cad8144..9eab20ad4 100644 --- a/Software/src/LiquidColorGenerator.cpp +++ b/Software/src/LiquidColorGenerator.cpp @@ -51,13 +51,16 @@ LiquidColorGenerator::LiquidColorGenerator(QObject *parent) : QObject(parent) connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateColor())); } -void LiquidColorGenerator::start() +void LiquidColorGenerator::start(const QColor& color) { DEBUG_LOW_LEVEL << Q_FUNC_INFO; m_isEnabled = true; reset(); + m_red = color.red(); + m_green = color.green(); + m_blue = color.blue(); updateColor(); } diff --git a/Software/src/LiquidColorGenerator.hpp b/Software/src/LiquidColorGenerator.hpp index 63cf19dc1..751819ad0 100644 --- a/Software/src/LiquidColorGenerator.hpp +++ b/Software/src/LiquidColorGenerator.hpp @@ -42,14 +42,14 @@ class LiquidColorGenerator : public QObject void updateColor(QColor color); public: - void start(); + void start(const QColor& color = Qt::black); void stop(); QColor current(); void reset(); public slots: void setSpeed(int value); - + private: int generateDelay(); QColor generateColor(); diff --git a/Software/src/MoodLamp.cpp b/Software/src/MoodLamp.cpp deleted file mode 100644 index 29640c3ec..000000000 --- a/Software/src/MoodLamp.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * MoodLamp.cpp - * - * Created on: 11.12.2011 - * Project: Lightpack - * - * Copyright (c) 2011 Mike Shatohin, mikeshatohin [at] gmail.com - * - * Lightpack a USB content-driving ambient lighting system - * - * Lightpack is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Lightpack is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) -#include -#endif -#include "MoodLamp.hpp" -#include "Settings.hpp" - -// Needed to get around the fact that the lamps are macros -class QRandomGeneratorShim { - -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) - QRandomGenerator m_rnd; -public: - void seed(quint32 seed) { - m_rnd.seed(seed); - } - - int bounded(int highest) { - return m_rnd.bounded(highest); - } - - int bounded(int lowest, int highest) { - return m_rnd.bounded(lowest, highest); - } -#else -public: - void seed(quint32 seed) { - qsrand(seed); - } - - int bounded(int highest) { - return qrand() % highest; - } - - int bounded(int lowest, int highest) { - return lowest + (qrand() % (highest - lowest + 1)); - } -#endif - -}; - /* - _OBJ_NAME_ : class name prefix - _LABEL_ : name string to be displayed - _BODY_ : class declaration body - */ -#define DECLARE_LAMP(_OBJ_NAME_,_LABEL_,_BODY_) \ -class _OBJ_NAME_ ## MoodLamp : public MoodLampBase \ -{\ -public:\ -_OBJ_NAME_ ## MoodLamp() : MoodLampBase() {};\ -~_OBJ_NAME_ ## MoodLamp() = default;\ -static const char* const name() { return _LABEL_; };\ -static MoodLampBase* create() { return new _OBJ_NAME_ ## MoodLamp(); };\ -\ -_BODY_\ -};\ -struct _OBJ_NAME_ ## Register {\ -_OBJ_NAME_ ## Register(){\ - g_moodLampMap.insert(lampID, MoodLampLampInfo(_OBJ_NAME_ ## MoodLamp::name(), _OBJ_NAME_ ## MoodLamp::create, lampID));\ - ++lampID;\ -}\ -};\ -_OBJ_NAME_ ## Register _OBJ_NAME_ ## Reg; - -using namespace SettingsScope; - -namespace { - static QMap g_moodLampMap; - static int lampID = 0; -} - -MoodLampBase* MoodLampBase::createWithID(const int id) { - QMap::const_iterator i = g_moodLampMap.find(id); - if (i != g_moodLampMap.end()) - return i.value().factory(); - qWarning() << Q_FUNC_INFO << "failed to find mood lamp ID: " << id; - return nullptr; -} - -void MoodLampBase::populateNameList(QList& list, int& recommended) -{ - list = g_moodLampMap.values(); - - if (list.size() > 0) - recommended = list[0].id; -} - -DECLARE_LAMP(Static, "Static (default)", -public: - int interval() const { return 50; }; - - bool shine(const QColor& newColor, QList& colors) - { - bool changed = false; - for (int i = 0; i < colors.size(); i++) - { - QRgb rgb = Settings::isLedEnabled(i) ? newColor.rgb() : 0; - changed = changed || (colors[i] != rgb); - colors[i] = rgb; - } - return changed; - }; -); - -DECLARE_LAMP(Fire, "Fire", -public: - void init() { - m_rnd.seed(QTime(0, 0, 0).secsTo(QTime::currentTime())); - }; - - bool shine(const QColor& newColor, QList& colors) { - if (colors.size() < 2) - return false; - - if (colors.size() > m_lightness.size()) { - const size_t oldSize = m_lightness.size(); - m_lightness.reserve(colors.size()); - for (size_t i = oldSize; i < colors.size(); ++i) - m_lightness << 0; - } - - // heavily inspired by FastLED Fire2012 demo - // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino - const int centerMax = colors.size() / 4; - const int middleLed = std::floor(colors.size() / 2); - const int sparkCount = colors.size() / 12; - - m_center += m_rnd.bounded(2) ? -1 : 1; - m_center = std::max(-centerMax, std::min(centerMax, m_center)); - - for (int i = 0; i < middleLed + m_center; ++i) { - const int minLightnessReduction = Cooling * std::pow((double)i / (middleLed + m_center), 3); - const int maxLightnessReduction = minLightnessReduction * 2; - const int lightnessReduction = minLightnessReduction + m_rnd.bounded(std::max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3; - m_lightness[i] = std::max(0, m_lightness[i] - lightnessReduction); - } - - for (int i = colors.size() - 1; i >= middleLed + m_center; --i) { - const int minLightnessReduction = Cooling * std::pow((double)(colors.size() - 1 - i) / (middleLed - m_center), 3); - const int maxLightnessReduction = minLightnessReduction * 2; - const int lightnessReduction = minLightnessReduction + m_rnd.bounded(std::max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3; - m_lightness[i] = std::max(0, m_lightness[i] - lightnessReduction); - } - - - for (int k = middleLed + m_center; k > 1; --k) - m_lightness[k] = (m_lightness[k - 1] + m_lightness[k - 2] * 2) / 3; - - for (int k = middleLed + m_center; k < colors.size() - 2; ++k) - m_lightness[k] = (m_lightness[k + 1] + m_lightness[k + 2] * 2) / 3; - - - if (m_rnd.bounded(2) == 0) { - int y = m_rnd.bounded(std::max(1, sparkCount)); - m_lightness[y] = std::max(SparkMax, (int)m_lightness[y] + (SparkMin + m_rnd.bounded(SparkMax - SparkMin))); - } - if (m_rnd.bounded(2) == 0) { - int z = colors.size() - 1 - m_rnd.bounded(std::max(1, sparkCount)); - m_lightness[z] = std::max(SparkMax, (int)m_lightness[z] + (SparkMin + m_rnd.bounded(SparkMax - SparkMin))); - } - - for (int i = 0; i < colors.size(); i++) - { - QColor color(newColor); - color.setHsl(color.hue(), color.saturation(), m_lightness[i]); - colors[i] = Settings::isLedEnabled(i) ? color.rgb() : 0; - } - return true; - }; -private: - QRandomGeneratorShim m_rnd; - QList m_lightness; - int m_center{ 0 }; - - const size_t Cooling = 8; - const int SparkMax = 160; - const int SparkMin = 100; -); - -DECLARE_LAMP(RGBLife, "RGB is Life", -public: - bool shine(const QColor& newColor, QList& colors) - { - bool changed = false; - const int degrees = 360 / colors.size(); - const int step = Speed * m_frames++; - for (int i = 0; i < colors.size(); i++) - { - QColor color(newColor); - color.setHsl(color.hue() + degrees * i + step, color.saturation(), color.lightness()); - QRgb rgb = Settings::isLedEnabled(i) ? color.rgb() : 0; - changed = changed || (colors[i] != rgb); - colors[i] = rgb; - } - if (step > 360) - m_frames = 0; - return changed; - }; -private: - const double Speed = 1.5; -); diff --git a/Software/src/MoodLamp.hpp b/Software/src/MoodLamp.hpp deleted file mode 100644 index 4d4947feb..000000000 --- a/Software/src/MoodLamp.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * MoodLamp.hpp - * - * Created on: 11.12.2011 - * Project: Lightpack - * - * Copyright (c) 2011 Mike Shatohin, mikeshatohin [at] gmail.com - * - * Lightpack a USB content-driving ambient lighting system - * - * Lightpack is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Lightpack is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#pragma once - -#include -#include - -class MoodLampBase; - -typedef MoodLampBase* (*LampFactory)(); - -struct MoodLampLampInfo { - MoodLampLampInfo() { this->name = ""; this->id = -1; this->factory = nullptr; } - MoodLampLampInfo(QString name, LampFactory factory, int id) { this->name = name; this->id = id; this->factory = factory; } - QString name; - LampFactory factory; - int id; -}; -Q_DECLARE_METATYPE(MoodLampLampInfo); - -class MoodLampBase -{ -public: - MoodLampBase() { init(); }; - virtual ~MoodLampBase() = default; - - static const char* const name() { return "NO_NAME"; }; - static MoodLampBase* create() { Q_ASSERT_X(false, "MoodLampBase::create()", "not implemented"); return nullptr; }; - static MoodLampBase* createWithID(const int id); - static void populateNameList(QList& list, int& recommended); - - virtual void init() {}; - virtual int interval() const { return DefaultInterval; }; - virtual bool shine(const QColor& newColor, QList& colors) = 0; -protected: - size_t m_frames{ 0 }; -private: - const int DefaultInterval = 33; -}; diff --git a/Software/src/MoodLampManager.cpp b/Software/src/MoodLampManager.cpp index 1b0331d17..1e567ab32 100644 --- a/Software/src/MoodLampManager.cpp +++ b/Software/src/MoodLampManager.cpp @@ -27,24 +27,29 @@ #include "MoodLampManager.hpp" #include "PrismatikMath.hpp" #include "Settings.hpp" +#include "QColorMetaWrapper.hpp" #include -#include "MoodLamp.hpp" +#include using namespace SettingsScope; -MoodLampManager::MoodLampManager(QObject *parent) : QObject(parent) +MoodLampManager::MoodLampManager(const QString& appDir, QObject *parent) : QObject(parent) { m_isMoodLampEnabled = false; m_timer.setTimerType(Qt::PreciseTimer); + + m_scriptDir = installScripts(appDir); + m_jsEngine = loadScripts(); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateColors())); initFromSettings(); } MoodLampManager::~MoodLampManager() { - if (m_lamp) - delete m_lamp; + m_jsEngine->collectGarbage(); + delete m_jsEngine; } void MoodLampManager::start(bool isEnabled) @@ -52,7 +57,7 @@ void MoodLampManager::start(bool isEnabled) DEBUG_LOW_LEVEL << Q_FUNC_INFO << isEnabled; m_isMoodLampEnabled = isEnabled; - + if (m_isMoodLampEnabled) { // This is usable if start is called after API unlock, and we should force set current color @@ -60,12 +65,12 @@ void MoodLampManager::start(bool isEnabled) } if (m_isMoodLampEnabled && m_isLiquidMode) - m_generator.start(); + m_generator.start(m_currentColor); else m_generator.stop(); - if (m_isMoodLampEnabled && m_lamp) - m_timer.start(m_lamp->interval()); + if (m_isMoodLampEnabled && !m_jsLamp.isUndefined()) + m_timer.start(m_jsLamp.property("interval").toUInt()); else m_timer.stop(); } @@ -85,7 +90,7 @@ void MoodLampManager::setLiquidMode(bool state) m_isLiquidMode = state; emit moodlampFrametime(1000); // reset FPS to 1 if (m_isLiquidMode && m_isMoodLampEnabled) - m_generator.start(); + m_generator.start(m_currentColor); else { m_generator.stop(); if (m_isMoodLampEnabled) @@ -136,39 +141,91 @@ void MoodLampManager::initFromSettings() setCurrentLamp(Settings::getMoodLampLamp()); } -void MoodLampManager::setCurrentLamp(const int id) +void MoodLampManager::setCurrentLamp(const QString& moduleName) { m_timer.stop(); - if (m_lamp) { - delete m_lamp; - m_lamp = nullptr; + if (!m_jsLamp.isUndefined()) { + m_jsLamp = QJSValue(); + m_jsEngine->collectGarbage(); + } + + if (!m_lamps.contains(moduleName)) { + qWarning() << Q_FUNC_INFO << moduleName << "unknown lamp"; + return; } - m_lamp = MoodLampBase::createWithID(id); + const MoodLampLampInfo& lampInfo = m_lamps[moduleName]; + + const QJSValue& newLamp = m_jsEngine->importModule(lampInfo.modulePath); + if (newLamp.isError()) { + qWarning() << Q_FUNC_INFO << QString("JS Error in %1:%2 %3") + .arg(newLamp.property("fileName").toString()) + .arg(newLamp.property("lineNumber").toInt()) + .arg(newLamp.toString()); + return; + } + m_lampModuleName = moduleName; + m_jsLamp = newLamp; emit moodlampFrametime(1000); // reset FPS to 1 - if (m_isMoodLampEnabled && m_lamp) - m_timer.start(m_lamp->interval()); + if (m_isMoodLampEnabled && !m_jsLamp.isUndefined()) { + updateColors(true); + m_timer.start(m_jsLamp.property("interval").toUInt()); + } } void MoodLampManager::updateColors(const bool forceUpdate) { DEBUG_HIGH_LEVEL << Q_FUNC_INFO << m_isLiquidMode; - QColor newColor; + QColor baseColor; if (m_isLiquidMode) - { - newColor = m_generator.current(); - } + baseColor = m_generator.current(); else - { - newColor = m_currentColor; + baseColor = m_currentColor; + + DEBUG_MID_LEVEL << Q_FUNC_INFO << baseColor.rgb(); + + bool changed = false; + if (!m_jsLamp.isUndefined()) { + QJSValueList args; + args << baseColor.rgb(); + + QVariantList outColors; + outColors.reserve(m_colors.size()); + for (const QRgb color : m_colors) + outColors << color; + args << m_jsEngine->toScriptValue(outColors); + + // Qt 5.15+ can auto convert m_colors + //args << m_jsEngine.toScriptValue(m_colors); + + const QJSValue& result = m_jsLamp.property("tick").call(args); + if (result.isError()) { + qWarning() << Q_FUNC_INFO << QString("JS Error in %1:%2 %3") + .arg(result.property("fileName").toString()) + .arg(result.property("lineNumber").toInt()) + .arg(result.toString()); + } + else if (result.isArray()) { + const QVariantList& colors = result.toVariant().toList(); + for (int i = 0; i < colors.size(); ++i) { + const QRgb newColor = Settings::isLedEnabled(i) ? colors[i].toInt() : 0; + changed = changed || (m_colors[i] != newColor); + m_colors[i] = newColor; + } + } + else + qWarning() << Q_FUNC_INFO << m_jsLamp.property("name").toString() << "tick() does not return [rgb1, rgb2, ...]"; + } + else { // fallback to static + for (QRgb& color : m_colors) { + changed = changed || (color != baseColor.rgb()); + color = baseColor.rgb(); + } } - DEBUG_MID_LEVEL << Q_FUNC_INFO << newColor.rgb(); - - bool changed = (m_lamp ? m_lamp->shine(newColor, m_colors) : false); if (changed || !m_isSendDataOnlyIfColorsChanged || forceUpdate) { emit updateLedsColors(m_colors); if (forceUpdate) { @@ -193,12 +250,84 @@ void MoodLampManager::initColors(int numberOfLeds) m_colors << 0; } -void MoodLampManager::requestLampList() +void MoodLampManager::requestLampList(const bool reloadScripts = false) { - QList list; - int recommended = 0; + DEBUG_LOW_LEVEL << Q_FUNC_INFO << reloadScripts; + + if (reloadScripts) { + m_lamps.clear(); + m_scriptDir.refresh(); + QJSEngine* oldEngine = m_jsEngine; + m_jsEngine = loadScripts(); + setCurrentLamp(m_lampModuleName); + delete oldEngine; + } + emit lampList(m_lamps.values()); +} - MoodLampBase::populateNameList(list, recommended); +QDir MoodLampManager::installScripts(const QString& appDir) +{ + const QString moodlampsDirName("moodlamps"); + QDir installDir(appDir); + if (!installDir.exists(moodlampsDirName)) + installDir.mkdir(moodlampsDirName); + installDir.cd(moodlampsDirName); + + const QDir resLampDir(":/" + moodlampsDirName, "*.mjs", QDir::IgnoreCase | QDir::Name, QDir::Files); + const QStringList& reslampScriptList = resLampDir.entryList(); + for (const QString& lampScript : reslampScriptList) { + if (QFile::exists(installDir.absoluteFilePath(lampScript)) && !QFile::remove(installDir.absoluteFilePath(lampScript))) { + qWarning() << Q_FUNC_INFO << "Could not remove" << lampScript; + continue; + } + if (!QFile::copy(":/" + moodlampsDirName + "/" + lampScript, installDir.absoluteFilePath(lampScript))) + qWarning() << Q_FUNC_INFO << "Cound not install" << lampScript; + } + return installDir; +} - emit lampList(list, recommended); +QJSEngine* MoodLampManager::loadScripts() +{ + QJSEngine* jsEngine = new QJSEngine(this); + jsEngine->globalObject().setProperty("QColor", jsEngine->newQMetaObject()); + jsEngine->globalObject().setProperty("QT_VERSION", QT_VERSION); + + const QRegularExpression cleanNameFilter("^[a-z0-9_]+\\.mjs$"); + const QStringList& lampScriptList = m_scriptDir.entryList().filter(cleanNameFilter); + bool enableConsole = false; + for (const QString& lampScript : lampScriptList) { + const QJSValue& jsModule = jsEngine->importModule(m_scriptDir.filePath(lampScript)); + if (jsModule.isError()) { + qWarning() << Q_FUNC_INFO << lampScript << QString("JS Error in %1:%2 %3") + .arg(jsModule.property("fileName").toString()) + .arg(jsModule.property("lineNumber").toInt()) + .arg(jsModule.toString()); + } + else if (!jsModule.hasOwnProperty("name") || !jsModule.property("name").isString()) + qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export const name = string\""; + else if (!jsModule.hasOwnProperty("tick") || !jsModule.property("tick").isCallable()) + qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export function tick(baseColor, colors){...}\""; + else if (!jsModule.hasOwnProperty("interval") || !jsModule.property("interval").isNumber() || jsModule.property("interval").toUInt() < 1) + qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export const interval = number\" with a valid (> 0) value" ; + else { + m_lamps.insert(lampScript, + MoodLampLampInfo( + jsModule.property("name").toString(), + lampScript, + m_scriptDir.filePath(lampScript) + ) + ); + enableConsole |= jsModule.hasOwnProperty("enableConsole") && jsModule.property("enableConsole").toBool(); + } + } + if (m_lamps.isEmpty()) + qWarning() << Q_FUNC_INFO << "No moodlamps loaded from " << m_scriptDir.absolutePath() << "; file names have to match" << cleanNameFilter.pattern(); + if (enableConsole) + jsEngine->installExtensions(QJSEngine::ConsoleExtension); + return jsEngine; +} + +QString MoodLampManager::scriptDir() const +{ + return m_scriptDir.absolutePath(); } diff --git a/Software/src/MoodLampManager.hpp b/Software/src/MoodLampManager.hpp index becd39e84..2bb748652 100644 --- a/Software/src/MoodLampManager.hpp +++ b/Software/src/MoodLampManager.hpp @@ -29,24 +29,36 @@ #include #include #include +#include +#include #include "LiquidColorGenerator.hpp" -#include "MoodLamp.hpp" + +struct MoodLampLampInfo { + MoodLampLampInfo() {} + MoodLampLampInfo(const QString& name, const QString& moduleName, const QString& modulePath) : + name(name), moduleName(moduleName), modulePath(modulePath) + {} + QString name; + QString moduleName; + QString modulePath; +}; +Q_DECLARE_METATYPE(MoodLampLampInfo); class MoodLampManager : public QObject { Q_OBJECT public: - explicit MoodLampManager(QObject *parent = 0); + explicit MoodLampManager(const QString& appDir, QObject *parent = 0); ~MoodLampManager(); signals: void updateLedsColors(const QList & colors); - void lampList(const QList &, int); + void lampList(const QList &); void moodlampFrametime(const double frameMs); public: void start(bool isMoodLampEnabled); - + QString scriptDir() const; // Common options void reset(); @@ -57,8 +69,8 @@ public slots: void settingsProfileChanged(const QString &profileName); void setNumberOfLeds(int value); void setCurrentColor(QColor color); - void setCurrentLamp(const int id); - void requestLampList(); + void setCurrentLamp(const QString& moduleName); + void requestLampList(const bool reloadScripts); void setSendDataOnlyIfColorsChanged(bool state); private slots: @@ -66,10 +78,10 @@ private slots: private: void initColors(int numberOfLeds); + QDir installScripts(const QString& appDir); + QJSEngine* loadScripts(); private: - MoodLampBase* m_lamp{ nullptr }; - LiquidColorGenerator m_generator; QList m_colors; @@ -81,4 +93,11 @@ private slots: QTimer m_timer; QElapsedTimer m_elapsedTimer; size_t m_frames{ 1 }; + + QJSEngine* m_jsEngine; + QJSValue m_jsLamp; + QDir m_scriptDir; + QString m_lampModuleName; + + QMap m_lamps; }; diff --git a/Software/src/QColorMetaWrapper.hpp b/Software/src/QColorMetaWrapper.hpp new file mode 100644 index 000000000..781fb87f9 --- /dev/null +++ b/Software/src/QColorMetaWrapper.hpp @@ -0,0 +1,94 @@ + +#include +#include + + +class QColorMetaWrapper : public QObject +{ + Q_OBJECT + +public: + // QColorMetaWrapper(const QColor &color) : m_color(color) {} + Q_INVOKABLE QColorMetaWrapper(const QString &name) : m_color(QColor(name)) {} + Q_INVOKABLE QColorMetaWrapper(int color) : m_color(QColor(color)) {} + Q_INVOKABLE QColorMetaWrapper(int r, int g, int b, int a = 255) : m_color(QColor(r, g, b, a)) {} + Q_INVOKABLE QColorMetaWrapper () {} + Q_INVOKABLE int alpha() const { return m_color.alpha(); } + Q_INVOKABLE float alphaF() const { return m_color.alphaF(); } + Q_INVOKABLE int black() const { return m_color.black(); } + Q_INVOKABLE float blackF() const { return m_color.blackF(); } + Q_INVOKABLE int blue() const { return m_color.blue(); } + Q_INVOKABLE float blueF() const { return m_color.blueF(); } + Q_INVOKABLE int cyan() const { return m_color.cyan(); } + Q_INVOKABLE float cyanF() const { return m_color.cyanF(); } + // Q_INVOKABLE QColorMetaWrapper darker(int factor = 200) const { return QColorMetaWrapper(m_color.darker(factor)); } + Q_INVOKABLE int green() const { return m_color.green(); } + Q_INVOKABLE float greenF() const { return m_color.greenF(); } + Q_INVOKABLE int hslHue() const { return m_color.hslHue(); } + Q_INVOKABLE float hslHueF() const { return m_color.hslHueF(); } + Q_INVOKABLE int hslSaturation() const { return m_color.hslSaturation(); } + Q_INVOKABLE float hslSaturationF() const { return m_color.hslSaturationF(); } + Q_INVOKABLE int hsvHue() const { return m_color.hsvHue(); } + Q_INVOKABLE float hsvHueF() const { return m_color.hsvHueF(); } + Q_INVOKABLE int hsvSaturation() const { return m_color.hsvSaturation(); } + Q_INVOKABLE float hsvSaturationF() const { return m_color.hsvSaturationF(); } + Q_INVOKABLE int hue() const { return m_color.hue(); } + Q_INVOKABLE float hueF() const { return m_color.hueF(); } + Q_INVOKABLE bool isValid() const { return m_color.isValid(); } + // Q_INVOKABLE QColorMetaWrapper lighter(int factor = 150) const { return QColorMetaWrapper(m_color.lighter(factor)); } + Q_INVOKABLE int lightness() const { return m_color.lightness(); } + Q_INVOKABLE float lightnessF() const { return m_color.lightnessF(); } + Q_INVOKABLE int magenta() const { return m_color.magenta(); } + Q_INVOKABLE float magentaF() const { return m_color.magentaF(); } + Q_INVOKABLE QString name() const { return m_color.name(); } + Q_INVOKABLE int red() const { return m_color.red(); } + Q_INVOKABLE float redF() const { return m_color.redF(); } + Q_INVOKABLE int rgb() const { return m_color.rgb(); } + Q_INVOKABLE int rgba() const { return m_color.rgba(); } + Q_INVOKABLE int saturation() const { return m_color.saturation(); } + Q_INVOKABLE float saturationF() const { return m_color.saturationF(); } + Q_INVOKABLE void setAlpha(int alpha) { return m_color.setAlpha(alpha); } + Q_INVOKABLE void setAlphaF(float alpha) { m_color.setAlphaF(alpha); } + Q_INVOKABLE void setBlue(int blue) { m_color.setBlue(blue); } + Q_INVOKABLE void setBlueF(float blue) { m_color.setBlueF(blue); } + Q_INVOKABLE void setCmyk(int c, int m, int y, int k, int a = 255) { m_color.setCmyk(c, m, y, k, a); } + Q_INVOKABLE void setCmykF(float c, float m, float y, float k, float a = 1.0) { m_color.setCmykF(c, m, y, k, a); } + Q_INVOKABLE void setGreen(int green) { m_color.setGreen(green); } + Q_INVOKABLE void setGreenF(float green) { m_color.setGreenF(green); } + Q_INVOKABLE void setHsl(int h, int s, int l, int a = 255) { m_color.setHsl(h, s, l, a); } + Q_INVOKABLE void setHslF(float h, float s, float l, float a = 1.0) { m_color.setHslF(h, s, l, a); } + Q_INVOKABLE void setHsv(int h, int s, int v, int a = 255) { m_color.setHsv(h, s, v, a); } + Q_INVOKABLE void setHsvF(float h, float s, float v, float a = 1.0) { m_color.setHsvF(h, s, v, a); } + Q_INVOKABLE void setNamedColor(const QString &name) { m_color.setNamedColor(name); } + Q_INVOKABLE void setRed(int red) { m_color.setRed(red); } + Q_INVOKABLE void setRedF(float red) { m_color.setRedF(red); } + Q_INVOKABLE void setRgb(int r, int g, int b, int a = 255) { m_color.setRgb(r, g, b, a); } + Q_INVOKABLE void setRgb(int rgb) { m_color.setRgb(rgb); } + Q_INVOKABLE void setRgbF(float r, float g, float b, float a = 1.0) { m_color.setRgbF(r, g, b, a); } + Q_INVOKABLE void setRgba(int rgba) { m_color.setRgba(rgba); } + // Q_INVOKABLE QColor toCmyk() const + // Q_INVOKABLE QColor toExtendedRgb() const + // Q_INVOKABLE QColor toHsl() const + // Q_INVOKABLE QColor toHsv() const + // Q_INVOKABLE QColor toRgb() const + Q_INVOKABLE int value() const { return m_color.value(); } + Q_INVOKABLE float valueF() const { return m_color.valueF(); } + Q_INVOKABLE int yellow() const { return m_color.yellow(); } + Q_INVOKABLE float yellowF() const { return m_color.yellowF(); } + + + // Q_INVOKABLE static QStringList colorNames() { return QColor::colorNames(); } + // Q_INVOKABLE static QColorMetaWrapper fromCmyk(int c, int m, int y, int k, int a = 255) { return QColorMetaWrapper(QColor::fromCmyk(c, m, y, k, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromCmykF(qreal c, qreal m, qreal y, qreal k, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromCmykF(c, m ,y, k, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromHsl(int h, int s, int l, int a = 255) { return QColorMetaWrapper(QColor::fromHsl(h, s, l, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromHslF(qreal h, qreal s, qreal l, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromHslF(h, s, l, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromHsv(int h, int s, int v, int a = 255) { return QColorMetaWrapper(QColor::fromHsv(h, s, v, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromHsvF(qreal h, qreal s, qreal v, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromHsvF(h, s, v, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromRgb(int rgb) { return QColorMetaWrapper(QColor::fromRgb(rgb)); } + // Q_INVOKABLE static QColorMetaWrapper fromRgb(int r, int g, int b, int a = 255) { return QColorMetaWrapper(QColor::fromRgb(r, g, b, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromRgbF(qreal r, qreal g, qreal b, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromRgbF(r, g, b, a)); } + // Q_INVOKABLE static QColorMetaWrapper fromRgba(int rgba) { return QColorMetaWrapper(QColor::fromRgba(rgba)); } + // Q_INVOKABLE static bool isValidColor(const QString &name) { return QColor::isValidColor(name); } +private: + QColor m_color; +}; diff --git a/Software/src/Settings.cpp b/Software/src/Settings.cpp index cd14534d9..41ee09bb0 100644 --- a/Software/src/Settings.cpp +++ b/Software/src/Settings.cpp @@ -1530,7 +1530,7 @@ QColor Settings::getMoodLampColor() void Settings::setMoodLampColor(QColor value) { DEBUG_LOW_LEVEL << Q_FUNC_INFO << value.name(); - setValue(Profile::Key::MoodLamp::Color, value.name() ); + setValue(Profile::Key::MoodLamp::Color, value.name()); m_this->moodLampColorChanged(value); } @@ -1547,13 +1547,14 @@ void Settings::setMoodLampSpeed(int value) m_this->moodLampSpeedChanged(value); } -int Settings::getMoodLampLamp() +QString Settings::getMoodLampLamp() { DEBUG_LOW_LEVEL << Q_FUNC_INFO; - return value(Profile::Key::MoodLamp::Lamp).toInt(); + const QString& val = value(Profile::Key::MoodLamp::Lamp).toString(); + return val.isEmpty() ? Profile::MoodLamp::LampDefault : val; } -void Settings::setMoodLampLamp(int value) +void Settings::setMoodLampLamp(const QString& value) { DEBUG_LOW_LEVEL << Q_FUNC_INFO; setValue(Profile::Key::MoodLamp::Lamp, value); @@ -2143,5 +2144,13 @@ void Settings::migrateSettings() setValueMain(Main::Key::MainConfigVersion, "4.0"); } + + const QString& val = value(Profile::Key::MoodLamp::Lamp).toString(); + if (val == "0") + setValue(Profile::Key::MoodLamp::Lamp, "static.mjs"); + else if (val == "1") + setValue(Profile::Key::MoodLamp::Lamp, "fire.mjs"); + else if (val == "2") + setValue(Profile::Key::MoodLamp::Lamp, "rgb_is_life.mjs"); } } /*SettingsScope*/ diff --git a/Software/src/Settings.hpp b/Software/src/Settings.hpp index d326712a5..9effd45d3 100644 --- a/Software/src/Settings.hpp +++ b/Software/src/Settings.hpp @@ -214,8 +214,8 @@ class Settings : public QObject static void setMoodLampColor(QColor color); static int getMoodLampSpeed(); static void setMoodLampSpeed(int value); - static int getMoodLampLamp(); - static void setMoodLampLamp(int value); + static QString getMoodLampLamp(); + static void setMoodLampLamp(const QString& value); #ifdef SOUNDVIZ_SUPPORT static int getSoundVisualizerDevice(); @@ -353,7 +353,7 @@ class Settings : public QObject void moodLampLiquidModeChanged(bool isLiquidMode); void moodLampColorChanged(const QColor color); void moodLampSpeedChanged(int value); - void moodLampLampChanged(int value); + void moodLampLampChanged(const QString& value); #ifdef SOUNDVIZ_SUPPORT void soundVisualizerDeviceChanged(int value); void soundVisualizerVisualizerChanged(int value); diff --git a/Software/src/SettingsDefaults.hpp b/Software/src/SettingsDefaults.hpp index 3955b7e75..8ab9b8def 100644 --- a/Software/src/SettingsDefaults.hpp +++ b/Software/src/SettingsDefaults.hpp @@ -190,7 +190,7 @@ static const double GammaMax = 10.0; // [MoodLamp] namespace MoodLamp { -static const int LampDefault = 0; +static const QString LampDefault = "static.mjs"; static const int SpeedMin = 1; static const int SpeedDefault = 50; static const int SpeedMax = 100; diff --git a/Software/src/SettingsWindow.cpp b/Software/src/SettingsWindow.cpp index 3cab18442..73a0a2655 100644 --- a/Software/src/SettingsWindow.cpp +++ b/Software/src/SettingsWindow.cpp @@ -222,6 +222,9 @@ void SettingsWindow::connectSignalsSlots() connect(ui->radioButton_LiquidColorMoodLampMode, SIGNAL(toggled(bool)), this, SLOT(onMoodLampLiquidMode_Toggled(bool))); connect(ui->horizontalSlider_MoodLampSpeed, SIGNAL(valueChanged(int)), this, SLOT(onMoodLampSpeed_valueChanged(int))); connect(ui->comboBox_MoodLampLamp, SIGNAL(currentIndexChanged(int)), this, SLOT(onMoodLampLamp_currentIndexChanged(int))); + connect(ui->toolButton_MoodLampReload, SIGNAL(clicked()), this, SLOT(onMoodLampReloadScripts_clicked())); + connect(ui->toolButton_MoodLampOpenDir, SIGNAL(clicked()), this, SLOT(onMoodLampOpenScripts_clicked())); + connect(ui->pushButton_MoodLampOpenReadme, SIGNAL(clicked()), this, SLOT(onMoodLampOpenReadme_clicked())); // Main options connect(ui->comboBox_LightpackModes, SIGNAL(currentIndexChanged(int)), this, SLOT(onLightpackModes_currentIndexChanged(int))); @@ -267,7 +270,7 @@ void SettingsWindow::connectSignalsSlots() #else ui->pushButton_SoundVizDeviceHelp->hide(); #endif // Q_OS_MACOS - + #endif// SOUNDVIZ_SUPPORT connect(ui->checkBox_ExpertModeEnabled, SIGNAL(toggled(bool)), this, SLOT(onExpertModeEnabled_Toggled(bool))); connect(ui->checkBox_KeepLightsOnAfterExit, SIGNAL(toggled(bool)), this, SLOT(onKeepLightsAfterExit_Toggled(bool))); @@ -496,7 +499,7 @@ int SettingsWindow::getLigtpackFirmwareVersionMajor() void SettingsWindow::onPostInit() { updateUiFromSettings(); this->requestFirmwareVersion(); - this->requestMoodLampLamps(); + this->requestMoodLampLamps(false); #ifdef SOUNDVIZ_SUPPORT this->requestSoundVizDevices(); this->requestSoundVizVisualizers(); @@ -986,18 +989,17 @@ void SettingsWindow::updateAvailableSoundVizVisualizers(const QList & lamps, int recommended) +void SettingsWindow::updateAvailableMoodLampLamps(const QList & lamps) { ui->comboBox_MoodLampLamp->blockSignals(true); ui->comboBox_MoodLampLamp->clear(); - int selectedLamp = Settings::getMoodLampLamp(); - if (selectedLamp == -1) selectedLamp = recommended; + QString selectedLamp = Settings::getMoodLampLamp(); int selectIndex = -1; for (int i = 0; i < lamps.size(); i++) { - ui->comboBox_MoodLampLamp->addItem(lamps[i].name, lamps[i].id); - if (lamps[i].id == selectedLamp) { + ui->comboBox_MoodLampLamp->addItem(lamps[i].name, lamps[i].moduleName); + + if (lamps[i].moduleName == selectedLamp) selectIndex = i; - } } ui->comboBox_MoodLampLamp->setCurrentIndex(selectIndex); ui->comboBox_MoodLampLamp->blockSignals(false); @@ -1076,7 +1078,7 @@ void SettingsWindow::ledDeviceOpenSuccess(bool isSuccess) } void SettingsWindow::ledDeviceCallSuccess(bool isSuccess) -{ +{ DEBUG_HIGH_LEVEL << Q_FUNC_INFO << isSuccess << m_backlightStatus << sender(); // If Backlight::StatusOff then nothings changed @@ -1135,7 +1137,7 @@ void SettingsWindow::ledDeviceFirmwareVersionUnofficialResult(const int version) } void SettingsWindow::refreshAmbilightEvaluated(double updateResultMs) -{ +{ DEBUG_HIGH_LEVEL << Q_FUNC_INFO << updateResultMs; double hz = 0; @@ -1161,7 +1163,7 @@ void SettingsWindow::refreshAmbilightEvaluated(double updateResultMs) DEBUG_HIGH_LEVEL << Q_FUNC_INFO << "Therotical Max Hz for led count and baud rate:" << theoreticalMaxHz << ledCount << baudRate; if (theoreticalMaxHz <= hz) qWarning() << Q_FUNC_INFO << hz << "FPS went over theoretical max of" << theoreticalMaxHz; - + const QPalette& defaultPalette = ui->label_GrabFrequency_txt_fps->palette(); QPalette palette = ui->label_GrabFrequency_value->palette(); @@ -1440,8 +1442,8 @@ void SettingsWindow::onMoodLampSpeed_valueChanged(int value) void SettingsWindow::onMoodLampLamp_currentIndexChanged(int index) { if (!updatingFromSettings) { - DEBUG_MID_LEVEL << Q_FUNC_INFO << index << ui->comboBox_MoodLampLamp->currentData().toInt(); - Settings::setMoodLampLamp(ui->comboBox_MoodLampLamp->currentData().toInt()); + DEBUG_MID_LEVEL << Q_FUNC_INFO << index << ui->comboBox_MoodLampLamp->currentData().toString(); + Settings::setMoodLampLamp(ui->comboBox_MoodLampLamp->currentData().toString()); } } @@ -1460,6 +1462,27 @@ void SettingsWindow::onMoodLampLiquidMode_Toggled(bool checked) } } +void SettingsWindow::onMoodLampReloadScripts_clicked() +{ + DEBUG_LOW_LEVEL << Q_FUNC_INFO; + + this->requestMoodLampLamps(true); +} + +void SettingsWindow::onMoodLampOpenScripts_clicked() +{ + DEBUG_LOW_LEVEL << Q_FUNC_INFO; + + this->openMoodLampScriptDir(); +} + +void SettingsWindow::onMoodLampOpenReadme_clicked() +{ + DEBUG_LOW_LEVEL << Q_FUNC_INFO; + + QDesktopServices::openUrl(QUrl("https://github.com/psieg/Lightpack/tree/master/Software/res/moodlamps")); +} + #ifdef SOUNDVIZ_SUPPORT void SettingsWindow::onSoundVizDevice_currentIndexChanged(int index) { @@ -1850,7 +1873,7 @@ void SettingsWindow::updateUiFromSettings() onLightpackModeChanged(mode); ui->checkBox_ExpertModeEnabled->setChecked (Settings::isExpertModeEnabled()); - + ui->checkBox_checkForUpdates->setChecked (Settings::isCheckForUpdatesEnabled()); ui->checkBox_installUpdates->setChecked (Settings::isInstallUpdatesEnabled()); @@ -1877,12 +1900,12 @@ void SettingsWindow::updateUiFromSettings() ui->radioButton_LuminosityDeadZone->setChecked (!Settings::isMinimumLuminosityEnabled()); // Check the selected moodlamp mode (setChecked(false) not working to select another) - ui->radioButton_ConstantColorMoodLampMode->setChecked (!Settings::isMoodLampLiquidMode()); + ui->radioButton_BaseColorMoodLamp->setChecked (!Settings::isMoodLampLiquidMode()); ui->radioButton_LiquidColorMoodLampMode->setChecked (Settings::isMoodLampLiquidMode()); ui->pushButton_SelectColorMoodLamp->setColor (Settings::getMoodLampColor()); ui->horizontalSlider_MoodLampSpeed->setValue (Settings::getMoodLampSpeed()); for (int i = 0; i < ui->comboBox_MoodLampLamp->count(); i++) { - if (ui->comboBox_MoodLampLamp->itemData(i).toInt() == Settings::getMoodLampLamp()) { + if (ui->comboBox_MoodLampLamp->itemData(i).toString() == Settings::getMoodLampLamp()) { ui->comboBox_MoodLampLamp->setCurrentIndex(i); break; } diff --git a/Software/src/SettingsWindow.hpp b/Software/src/SettingsWindow.hpp index 405f1440a..20485a2c1 100644 --- a/Software/src/SettingsWindow.hpp +++ b/Software/src/SettingsWindow.hpp @@ -78,7 +78,7 @@ class SettingsWindow : public QMainWindow { void requestSoundVizDevices(); void requestSoundVizVisualizers(); #endif - void requestMoodLampLamps(); + void requestMoodLampLamps(const bool reloadScripts); void recreateLedDevice(); void resultBacklightStatus(Backlight::Status); void backlightStatusChanged(Backlight::Status); @@ -88,6 +88,7 @@ class SettingsWindow : public QMainWindow { void updateApiKey(QString key); void updateApiDeviceNumberOfLeds(int value); void reloadPlugins(); + void openMoodLampScriptDir(); public slots: void ledDeviceOpenSuccess(bool isSuccess); @@ -112,7 +113,7 @@ public slots: void onPingDeviceEverySecond_Toggled(bool state); void processMessage(const QString &message); - void updateAvailableMoodLampLamps(const QList & lamps, int recommended); + void updateAvailableMoodLampLamps(const QList & lamps); #ifdef SOUNDVIZ_SUPPORT void updateAvailableSoundVizDevices(const QList & devices, int recommended); void updateAvailableSoundVizVisualizers(const QList & visualizers, int recommended); @@ -138,6 +139,9 @@ private slots: void onMoodLampSpeed_valueChanged(int value); void onMoodLampLamp_currentIndexChanged(int index); void onMoodLampLiquidMode_Toggled(bool isLiquidMode); + void onMoodLampReloadScripts_clicked(); + void onMoodLampOpenScripts_clicked(); + void onMoodLampOpenReadme_clicked(); #ifdef SOUNDVIZ_SUPPORT void onSoundVizDevice_currentIndexChanged(int index); void onSoundVizVisualizer_currentIndexChanged(int index); diff --git a/Software/src/SettingsWindow.ui b/Software/src/SettingsWindow.ui index 278f6b2f1..8c564c437 100644 --- a/Software/src/SettingsWindow.ui +++ b/Software/src/SettingsWindow.ui @@ -720,14 +720,69 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< 0 - + + + + + + + + Reload lamp list + + + Reload + + + + :/icons/refresh.png:/icons/refresh.png + + + + + + + Open lamp script directory + + + Add more + + + + :/icons/profile_new.png:/icons/profile_new.png + + + + + + + + 0 + 0 + + + + + + + + :/icons/help.png:/icons/help.png + + + true + + + + - + + + <html><head/><body><p>A constant base color that will be passed to moodlamps as an optional seed/starting color.</p><p>Some lamps might not use it, some might use it partially (a combination of R,G,B,H,S,L,V components)</p></body></html> + - Constant color: + Base color: true @@ -739,6 +794,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< + + <html><head/><body><p>A constant base color that will be passed to moodlamps as an optional seed/starting color.</p><p>Some lamps might not use it, some might use it partially (a combination of R,G,B,H,S,L,V components)</p></body></html> + @@ -769,8 +827,11 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< + + Makes the base color smoothly cycle through a dozen of colors at a certain rate. + - Change color with rate: + Change base color with rate: @@ -781,6 +842,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< 8 + + Makes the base color smoothly cycle through a dozen of colors at a certain rate. + margin-bottom:0.4em; margin-left:0.5em @@ -797,6 +861,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< false + + Makes the base color smoothly cycle through a dozen of colors at a certain rate. + 1 @@ -815,6 +882,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< 8 + + Makes the base color smoothly cycle through a dozen of colors at a certain rate. + margin-bottom:0.4em; @@ -3087,7 +3157,11 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...< radioButton_GrabWidgetsDontShow radioButton_Colored radioButton_White - radioButton_ConstantColorMoodLampMode + comboBox_MoodLampLamp + toolButton_MoodLampReload + toolButton_MoodLampOpenDir + pushButton_MoodLampOpenReadme + radioButton_BaseColorMoodLamp pushButton_SelectColorMoodLamp radioButton_LiquidColorMoodLampMode horizontalSlider_MoodLampSpeed diff --git a/Software/src/src.pro b/Software/src/src.pro index 66546f492..3ea473463 100644 --- a/Software/src/src.pro +++ b/Software/src/src.pro @@ -14,7 +14,7 @@ CONFIG(msvc) { } DESTDIR = ../bin TEMPLATE = app -QT += network widgets +QT += network widgets qml win32 { QT += serialport } @@ -279,7 +279,6 @@ SOURCES += \ ApiServer.cpp \ ApiServerSetColorTask.cpp \ MoodLampManager.cpp \ - MoodLamp.cpp \ LiquidColorGenerator.cpp \ LedDeviceManager.cpp \ SelectWidget.cpp \ @@ -335,7 +334,7 @@ HEADERS += \ ../../CommonHeaders/COMMANDS.h \ ../../CommonHeaders/USB_ID.h \ MoodLampManager.hpp \ - MoodLamp.hpp \ + QColorMetaWrapper.hpp \ LiquidColorGenerator.hpp \ LedDeviceManager.hpp \ SelectWidget.hpp \