Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 806d701535 | |||
| 3644001dbc |
32
.drone.yml
32
.drone.yml
@@ -14,7 +14,7 @@ steps:
|
||||
- ls -la dist
|
||||
|
||||
- name: build-deb-armhf
|
||||
image: cache.coadcorp.com/library/buildpack-deps:jammy
|
||||
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
ARM_CFLAGS: -march=armv6 -mfpu=vfp -mfloat-abi=hard -marm
|
||||
@@ -22,14 +22,12 @@ steps:
|
||||
- dpkg --add-architecture armhf
|
||||
- |
|
||||
cat > /etc/apt/sources.list <<'EOF'
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse
|
||||
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
|
||||
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
|
||||
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
|
||||
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
|
||||
deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
|
||||
deb [arch=amd64] http://deb.debian.org/debian bullseye main contrib non-free
|
||||
deb [arch=amd64] http://deb.debian.org/debian bullseye-updates main contrib non-free
|
||||
deb [arch=amd64] http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||
deb [arch=armhf] http://deb.debian.org/debian bullseye main contrib non-free
|
||||
deb [arch=armhf] http://deb.debian.org/debian bullseye-updates main contrib non-free
|
||||
deb [arch=armhf] http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||
EOF
|
||||
- apt-get update
|
||||
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf qtbase5-dev:armhf libqt5core5a:armhf libqt5gui5:armhf libqt5widgets5:armhf libqt5network5:armhf libexif-dev:armhf libmosquitto-dev:armhf qt5-image-formats-plugins:armhf
|
||||
@@ -37,7 +35,7 @@ steps:
|
||||
- ls -la dist
|
||||
|
||||
- name: build-deb-arm64
|
||||
image: cache.coadcorp.com/library/buildpack-deps:jammy
|
||||
image: cache.coadcorp.com/library/buildpack-deps:bullseye
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
ARM64_CFLAGS: -march=armv8-a
|
||||
@@ -45,14 +43,12 @@ steps:
|
||||
- dpkg --add-architecture arm64
|
||||
- |
|
||||
cat > /etc/apt/sources.list <<'EOF'
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
|
||||
deb [arch=amd64] http://archive.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse
|
||||
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
|
||||
deb [arch=amd64] http://deb.debian.org/debian bullseye main contrib non-free
|
||||
deb [arch=amd64] http://deb.debian.org/debian bullseye-updates main contrib non-free
|
||||
deb [arch=amd64] http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||
deb [arch=arm64] http://deb.debian.org/debian bullseye main contrib non-free
|
||||
deb [arch=arm64] http://deb.debian.org/debian bullseye-updates main contrib non-free
|
||||
deb [arch=arm64] http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||
EOF
|
||||
- apt-get update
|
||||
- apt-get install -y --no-install-recommends build-essential qt5-qmake qtbase5-dev qtbase5-dev-tools libexif-dev qt5-image-formats-plugins libmosquitto-dev dpkg-dev fakeroot ca-certificates gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qtbase5-dev:arm64 libqt5core5a:arm64 libqt5gui5:arm64 libqt5widgets5:arm64 libqt5network5:arm64 libexif-dev:arm64 libmosquitto-dev:arm64 qt5-image-formats-plugins:arm64
|
||||
|
||||
21
INSTALL.md
21
INSTALL.md
@@ -6,6 +6,8 @@ This project depends on the following dynamically linked libraries:
|
||||
|
||||
* qt5
|
||||
* libexif
|
||||
* qt5-image-formats-plugins
|
||||
* libmosquitto1
|
||||
|
||||
### OSX
|
||||
|
||||
@@ -13,28 +15,29 @@ This project depends on the following dynamically linked libraries:
|
||||
brew install qt libexif
|
||||
```
|
||||
|
||||
### Raspbian Stretch
|
||||
### Debian / Ubuntu / Raspberry Pi OS (runtime dependencies)
|
||||
|
||||
```
|
||||
brew install qt5 libexif12
|
||||
sudo apt-get install -y qt5-image-formats-plugins libmosquitto1
|
||||
```
|
||||
|
||||
## Extract binaries
|
||||
### Raspberry Pi Zero / Raspbian (additional image format support)
|
||||
|
||||
```
|
||||
tar xf slide_<arch>_<version>.tar.gz
|
||||
sudo apt-get install -y qt5-image-formats-plugins libmosquitto1 libmng1
|
||||
```
|
||||
|
||||
## Move binary to executable folder
|
||||
## Install .deb package
|
||||
|
||||
### OSX
|
||||
Use apt so dependencies are resolved automatically:
|
||||
|
||||
```
|
||||
mv slide_<version>/slide.app/Contents/MacOS/slide /usr/local/bin/
|
||||
sudo apt-get install ./slide_<version>_<arch>.deb
|
||||
```
|
||||
|
||||
### Linux
|
||||
If you must use dpkg, install runtime dependencies first:
|
||||
|
||||
```
|
||||
mv slide_<version>/slide /usr/bin/
|
||||
sudo apt-get install -y qt5-image-formats-plugins libmosquitto1
|
||||
sudo dpkg -i slide_<version>_<arch>.deb
|
||||
```
|
||||
|
||||
12
README.md
12
README.md
@@ -136,6 +136,7 @@ Example:
|
||||
"host": "mqtt.local",
|
||||
"port": 1883,
|
||||
"topic": "slide/control",
|
||||
"immichTopic": "slide/immich",
|
||||
"clientId": "slide-frame",
|
||||
"username": "slide",
|
||||
"password": "secret",
|
||||
@@ -151,6 +152,16 @@ Commands:
|
||||
* `next` / `next-image` — advance to next image
|
||||
* `next-folder` — jump to next configured path (if multiple paths are configured)
|
||||
* `restart` / `reset` — recreate the selector and restart playback
|
||||
If `immichTopic` is not set, it defaults to `<topic>/immich`.
|
||||
|
||||
Immich control topic (`immichTopic`):
|
||||
* `album:<id>` or `albumIds:id1,id2` — filter to one or more album IDs
|
||||
* `person:<id>` or `personIds:id1,id2` — filter to one or more person IDs
|
||||
* `reset` / `clear` — clear album/person filters
|
||||
* JSON payloads are also accepted, for example:
|
||||
```
|
||||
{"albumIds":["..."],"personIds":["..."],"order":"desc","size":"fullsize"}
|
||||
```
|
||||
|
||||
### Immich configuration (lightweight + low power)
|
||||
|
||||
@@ -195,6 +206,7 @@ Immich settings:
|
||||
* `url`: base Immich server URL (the integration appends `/api` automatically if missing).
|
||||
* `apiKey`: Immich API key (needs `asset.view`, and `asset.download` if `size` is `original`).
|
||||
* `albumId` or `albumIds`: optional album filters.
|
||||
* `personId` or `personIds`: optional person filters.
|
||||
* `size`: `"fullsize"`, `"preview"`, `"thumbnail"`, or `"original"` (original uses the download endpoint).
|
||||
* `order`: `"asc"` or `"desc"` ordering for asset search.
|
||||
* `pageSize`: assets fetched per page.
|
||||
|
||||
@@ -66,4 +66,4 @@ Description: Lightweight slideshow for photo frames
|
||||
Simple, lightweight slideshow designed for low power devices.
|
||||
EOF
|
||||
|
||||
dpkg-deb --build "$STAGE_DIR" "$DIST_DIR/${PACKAGE_NAME}_${VERSION}_${ARCH}.deb"
|
||||
dpkg-deb --build -Zgzip "$STAGE_DIR" "$DIST_DIR/${PACKAGE_NAME}_${VERSION}_${ARCH}.deb"
|
||||
|
||||
@@ -101,6 +101,14 @@ ImmichConfig ParseImmichConfigObject(QJsonObject immichJson) {
|
||||
if(albumIds.size() > 0)
|
||||
config.albumIds = albumIds;
|
||||
|
||||
std::string personId = ParseJSONString(immichJson, "personId");
|
||||
if(!personId.empty())
|
||||
config.personIds.push_back(personId);
|
||||
|
||||
std::vector<std::string> personIds = ParseJSONStrings(immichJson, "personIds");
|
||||
if(personIds.size() > 0)
|
||||
config.personIds = personIds;
|
||||
|
||||
SetJSONInt(config.pageSize, immichJson, "pageSize");
|
||||
SetJSONInt(config.maxAssets, immichJson, "maxAssets");
|
||||
SetJSONInt(config.cacheMaxMB, immichJson, "cacheMaxMB");
|
||||
@@ -123,6 +131,10 @@ MqttConfig ParseMqttConfigObject(QJsonObject mqttJson) {
|
||||
if(!topic.empty())
|
||||
config.topic = topic;
|
||||
|
||||
std::string immichTopic = ParseJSONString(mqttJson, "immichTopic");
|
||||
if(!immichTopic.empty())
|
||||
config.immichTopic = immichTopic;
|
||||
|
||||
std::string clientId = ParseJSONString(mqttJson, "clientId");
|
||||
if(!clientId.empty())
|
||||
config.clientId = clientId;
|
||||
@@ -139,6 +151,9 @@ MqttConfig ParseMqttConfigObject(QJsonObject mqttJson) {
|
||||
SetJSONInt(config.keepAlive, mqttJson, "keepAlive");
|
||||
SetJSONInt(config.qos, mqttJson, "qos");
|
||||
|
||||
if(config.immichTopic.empty() && !config.topic.empty())
|
||||
config.immichTopic = config.topic + "/immich";
|
||||
|
||||
if(!config.host.empty() && !config.topic.empty())
|
||||
config.enabled = true;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ struct ImmichConfig {
|
||||
std::string url = "";
|
||||
std::string apiKey = "";
|
||||
std::vector<std::string> albumIds;
|
||||
std::vector<std::string> personIds;
|
||||
std::string size = "fullsize";
|
||||
std::string order = "desc";
|
||||
int pageSize = 200;
|
||||
@@ -40,6 +41,13 @@ struct ImmichConfig {
|
||||
if (albumIds[i] != b.albumIds[i])
|
||||
return false;
|
||||
}
|
||||
if (personIds.size() != b.personIds.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < personIds.size(); ++i)
|
||||
{
|
||||
if (personIds[i] != b.personIds[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,6 +62,7 @@ struct MqttConfig {
|
||||
std::string host = "localhost";
|
||||
int port = 1883;
|
||||
std::string topic = "slide/control";
|
||||
std::string immichTopic = "";
|
||||
std::string clientId = "slide";
|
||||
std::string username = "";
|
||||
std::string password = "";
|
||||
@@ -66,6 +75,7 @@ struct MqttConfig {
|
||||
host == b.host &&
|
||||
port == b.port &&
|
||||
topic == b.topic &&
|
||||
immichTopic == b.immichTopic &&
|
||||
clientId == b.clientId &&
|
||||
username == b.username &&
|
||||
password == b.password &&
|
||||
|
||||
@@ -105,6 +105,14 @@ QVector<ImmichAsset> ImmichClient::fetchAssets()
|
||||
bool triedZero = false;
|
||||
int page = 1;
|
||||
|
||||
if (ShouldLog())
|
||||
{
|
||||
Log("Immich search: size=", config.size, ", order=", config.order,
|
||||
", pageSize=", pageSize, ", maxAssets=", maxAssets,
|
||||
", albumIds=", config.albumIds.size(),
|
||||
", personIds=", config.personIds.size());
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
QJsonObject body;
|
||||
@@ -121,6 +129,13 @@ QVector<ImmichAsset> ImmichClient::fetchAssets()
|
||||
ids.append(QString::fromStdString(id));
|
||||
body["albumIds"] = ids;
|
||||
}
|
||||
if (config.personIds.size() > 0)
|
||||
{
|
||||
QJsonArray ids;
|
||||
for (const auto &id : config.personIds)
|
||||
ids.append(QString::fromStdString(id));
|
||||
body["personIds"] = ids;
|
||||
}
|
||||
|
||||
QByteArray response = postJson(apiUrl("/search/metadata"), body, nullptr, kMetadataTimeoutMs);
|
||||
if (response.isEmpty())
|
||||
@@ -134,6 +149,7 @@ QVector<ImmichAsset> ImmichClient::fetchAssets()
|
||||
QJsonObject assetsObj = root["assets"].toObject();
|
||||
QJsonArray items = assetsObj["items"].toArray();
|
||||
int total = assetsObj["total"].toInt();
|
||||
Log("Immich page ", page, ": ", items.size(), " assets (total ", total, ")");
|
||||
if (items.isEmpty())
|
||||
{
|
||||
if (total > 0 && page == 1 && !triedZero)
|
||||
@@ -194,6 +210,7 @@ bool ImmichClient::downloadAsset(const QString &assetId, QByteArray &data, QStri
|
||||
url.setQuery(query);
|
||||
}
|
||||
|
||||
Log("Immich download asset ", assetId.toStdString(), " (", size.toStdString(), ")");
|
||||
QByteArray payload = getBytes(url, &contentType, kAssetTimeoutMs);
|
||||
if (payload.isEmpty())
|
||||
return false;
|
||||
@@ -297,7 +314,10 @@ QString ImmichAssetCache::getCachedPath(const QString &assetId, const QString &a
|
||||
|
||||
QString existing = findExisting(assetId);
|
||||
if (!existing.isEmpty())
|
||||
{
|
||||
Log("Immich cache hit: ", assetId.toStdString());
|
||||
return existing;
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
QString contentType;
|
||||
@@ -318,6 +338,8 @@ QString ImmichAssetCache::getCachedPath(const QString &assetId, const QString &a
|
||||
if (!file.commit())
|
||||
return "";
|
||||
|
||||
Log("Immich cached asset: ", assetId.toStdString(), " -> ", filePath.toStdString());
|
||||
|
||||
if (cacheMaxBytes > 0)
|
||||
{
|
||||
if (!cacheSizeKnown)
|
||||
|
||||
239
src/main.cpp
239
src/main.cpp
@@ -19,6 +19,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
void usage(std::string programName) {
|
||||
std::cerr << "Usage: " << programName << " [-t rotation_seconds] [-T transition_seconds] [-h/--overlay-color #rrggbb] [-a aspect('l','p','a', 'm')] [-o background_opacity(0..255)] [-b blur_radius] -p image_folder [-r] [-s] [-S] [-v] [--verbose] [--stretch] [-c config_file_path]" << std::endl;
|
||||
@@ -243,6 +246,216 @@ void ReloadConfigIfNeeded(AppConfig &appConfig, MainWindow &w, ImageSwitcher *sw
|
||||
}
|
||||
}
|
||||
|
||||
static bool ParseBooleanString(const QString &value, bool &outValue)
|
||||
{
|
||||
QString v = value.trimmed().toLower();
|
||||
if (v == "true" || v == "1" || v == "yes" || v == "on")
|
||||
{
|
||||
outValue = true;
|
||||
return true;
|
||||
}
|
||||
if (v == "false" || v == "0" || v == "no" || v == "off")
|
||||
{
|
||||
outValue = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<std::string> SplitCsv(const QString &value)
|
||||
{
|
||||
std::vector<std::string> output;
|
||||
QStringList parts = value.split(',', Qt::SkipEmptyParts);
|
||||
for (const auto &part : parts)
|
||||
{
|
||||
QString trimmed = part.trimmed();
|
||||
if (!trimmed.isEmpty())
|
||||
output.push_back(trimmed.toStdString());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static bool ApplyImmichPayload(ImmichConfig &config, const QString &payload)
|
||||
{
|
||||
bool changed = false;
|
||||
QString trimmed = payload.trimmed();
|
||||
if (trimmed.isEmpty())
|
||||
return false;
|
||||
|
||||
if (trimmed.startsWith("{"))
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(trimmed.toUtf8());
|
||||
if (!doc.isObject())
|
||||
return false;
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
if (obj.contains("albumId") && obj["albumId"].isString())
|
||||
{
|
||||
config.albumIds = { obj["albumId"].toString().toStdString() };
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("albumIds") && obj["albumIds"].isArray())
|
||||
{
|
||||
config.albumIds.clear();
|
||||
QJsonArray arr = obj["albumIds"].toArray();
|
||||
for (const auto &value : arr)
|
||||
{
|
||||
if (value.isString())
|
||||
config.albumIds.push_back(value.toString().toStdString());
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("personId") && obj["personId"].isString())
|
||||
{
|
||||
config.personIds = { obj["personId"].toString().toStdString() };
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("personIds") && obj["personIds"].isArray())
|
||||
{
|
||||
config.personIds.clear();
|
||||
QJsonArray arr = obj["personIds"].toArray();
|
||||
for (const auto &value : arr)
|
||||
{
|
||||
if (value.isString())
|
||||
config.personIds.push_back(value.toString().toStdString());
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("order") && obj["order"].isString())
|
||||
{
|
||||
config.order = obj["order"].toString().toStdString();
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("size") && obj["size"].isString())
|
||||
{
|
||||
config.size = obj["size"].toString().toStdString();
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("pageSize") && obj["pageSize"].isDouble())
|
||||
{
|
||||
config.pageSize = (int)obj["pageSize"].toDouble();
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("maxAssets") && obj["maxAssets"].isDouble())
|
||||
{
|
||||
config.maxAssets = (int)obj["maxAssets"].toDouble();
|
||||
changed = true;
|
||||
}
|
||||
if (obj.contains("includeArchived"))
|
||||
{
|
||||
if (obj["includeArchived"].isBool())
|
||||
{
|
||||
config.includeArchived = obj["includeArchived"].toBool();
|
||||
changed = true;
|
||||
}
|
||||
else if (obj["includeArchived"].isString())
|
||||
{
|
||||
bool parsed = false;
|
||||
bool boolValue = false;
|
||||
parsed = ParseBooleanString(obj["includeArchived"].toString(), boolValue);
|
||||
if (parsed)
|
||||
{
|
||||
config.includeArchived = boolValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj.contains("reset") && obj["reset"].isBool() && obj["reset"].toBool())
|
||||
{
|
||||
config.albumIds.clear();
|
||||
config.personIds.clear();
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
QString key;
|
||||
QString value;
|
||||
int idx = trimmed.indexOf('=');
|
||||
if (idx < 0)
|
||||
idx = trimmed.indexOf(':');
|
||||
if (idx < 0)
|
||||
idx = trimmed.indexOf(' ');
|
||||
|
||||
if (idx >= 0)
|
||||
{
|
||||
key = trimmed.left(idx).trimmed().toLower();
|
||||
value = trimmed.mid(idx + 1).trimmed();
|
||||
}
|
||||
else
|
||||
{
|
||||
key = trimmed.toLower();
|
||||
}
|
||||
|
||||
if (key == "reset" || key == "clear" || key == "all")
|
||||
{
|
||||
config.albumIds.clear();
|
||||
config.personIds.clear();
|
||||
return true;
|
||||
}
|
||||
if (key == "album" || key == "albumid")
|
||||
{
|
||||
config.albumIds = { value.toStdString() };
|
||||
return true;
|
||||
}
|
||||
if (key == "albums" || key == "albumids")
|
||||
{
|
||||
config.albumIds = SplitCsv(value);
|
||||
return true;
|
||||
}
|
||||
if (key == "person" || key == "personid")
|
||||
{
|
||||
config.personIds = { value.toStdString() };
|
||||
return true;
|
||||
}
|
||||
if (key == "persons" || key == "personids")
|
||||
{
|
||||
config.personIds = SplitCsv(value);
|
||||
return true;
|
||||
}
|
||||
if (key == "order")
|
||||
{
|
||||
config.order = value.toStdString();
|
||||
return true;
|
||||
}
|
||||
if (key == "size")
|
||||
{
|
||||
config.size = value.toStdString();
|
||||
return true;
|
||||
}
|
||||
if (key == "includearchived")
|
||||
{
|
||||
bool boolValue = false;
|
||||
if (ParseBooleanString(value, boolValue))
|
||||
{
|
||||
config.includeArchived = boolValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (key == "pagesize")
|
||||
{
|
||||
bool ok = false;
|
||||
int parsed = value.toInt(&ok);
|
||||
if (ok)
|
||||
{
|
||||
config.pageSize = parsed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (key == "maxassets")
|
||||
{
|
||||
bool ok = false;
|
||||
int parsed = value.toInt(&ok);
|
||||
if (ok)
|
||||
{
|
||||
config.maxAssets = parsed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
@@ -292,6 +505,32 @@ int main(int argc, char *argv[])
|
||||
std::unique_ptr<ImageSelector> newSelector = GetSelectorForApp(appConfig);
|
||||
switcher.restart(newSelector);
|
||||
});
|
||||
QObject::connect(mqttController.get(), &MqttController::immichControl, [&appConfig, &switcher](const QString &payload) {
|
||||
bool updated = false;
|
||||
for (int i = 0; i < appConfig.paths.count(); ++i)
|
||||
{
|
||||
if (!appConfig.paths[i].immich.enabled)
|
||||
continue;
|
||||
ImmichConfig newConfig = appConfig.paths[i].immich;
|
||||
if (ApplyImmichPayload(newConfig, payload))
|
||||
{
|
||||
appConfig.paths[i].immich = newConfig;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated)
|
||||
{
|
||||
Log("MQTT immich update applied.");
|
||||
std::unique_ptr<ImageSelector> newSelector = GetSelectorForApp(appConfig);
|
||||
switcher.setImageSelector(newSelector);
|
||||
if (!switcher.isPaused())
|
||||
switcher.scheduleImageUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("MQTT immich update ignored: ", payload.toStdString());
|
||||
}
|
||||
});
|
||||
mqttController->start();
|
||||
}
|
||||
switcher.start();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QTransform>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
@@ -170,11 +171,11 @@ void MainWindow::updateImage()
|
||||
return;
|
||||
|
||||
QLabel *label = this->findChild<QLabel*>("image");
|
||||
const QPixmap* oldImage = label->pixmap();
|
||||
if (oldImage != NULL && transitionSeconds > 0)
|
||||
QPixmap oldImage = label->pixmap(Qt::ReturnByValue);
|
||||
if (!oldImage.isNull() && transitionSeconds > 0)
|
||||
{
|
||||
QPalette palette;
|
||||
palette.setBrush(QPalette::Background, *oldImage);
|
||||
palette.setBrush(QPalette::Window, oldImage);
|
||||
this->setPalette(palette);
|
||||
}
|
||||
|
||||
@@ -213,7 +214,7 @@ void MainWindow::updateImage()
|
||||
|
||||
label->setPixmap(background);
|
||||
|
||||
if (oldImage != NULL && transitionSeconds > 0)
|
||||
if (!oldImage.isNull() && transitionSeconds > 0)
|
||||
{
|
||||
auto effect = new QGraphicsOpacityEffect(label);
|
||||
effect->setOpacity(0.0);
|
||||
@@ -273,9 +274,9 @@ QPixmap MainWindow::getBlurredBackground(const QPixmap& originalSize, const QPix
|
||||
|
||||
QPixmap MainWindow::getRotatedPixmap(const QPixmap& p)
|
||||
{
|
||||
QMatrix matrix;
|
||||
matrix.rotate(currentImage.rotation);
|
||||
return p.transformed(matrix);
|
||||
QTransform transform;
|
||||
transform.rotate(currentImage.rotation);
|
||||
return p.transformed(transform);
|
||||
}
|
||||
|
||||
QPixmap MainWindow::getScaledPixmap(const QPixmap& p)
|
||||
@@ -325,12 +326,12 @@ void MainWindow::drawBackground(const QPixmap& originalSize, const QPixmap& scal
|
||||
QPixmap background = blur(originalSize.scaledToHeight(height()));
|
||||
QRect rect((background.width() - width())/2, 0, width(), height());
|
||||
background = background.copy(rect);
|
||||
palette.setBrush(QPalette::Background, background);
|
||||
palette.setBrush(QPalette::Window, background);
|
||||
} else {
|
||||
QPixmap background = blur(originalSize.scaledToHeight(height()));
|
||||
QRect rect((background.width() - width())/2, 0, width(), height());
|
||||
background = background.copy(rect);
|
||||
palette.setBrush(QPalette::Background, background);
|
||||
palette.setBrush(QPalette::Window, background);
|
||||
}
|
||||
this->setPalette(palette);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ MqttController::MqttController(const MqttConfig &configIn, QObject *parent)
|
||||
mosquitto_lib_init();
|
||||
g_mqttInitialized = true;
|
||||
}
|
||||
controlTopic = QString::fromStdString(config.topic);
|
||||
immichTopic = QString::fromStdString(config.immichTopic);
|
||||
}
|
||||
|
||||
MqttController::~MqttController()
|
||||
@@ -103,6 +105,18 @@ void MqttController::subscribe()
|
||||
{
|
||||
Log("MQTT subscribed to ", config.topic);
|
||||
}
|
||||
if (!config.immichTopic.empty() && config.immichTopic != config.topic)
|
||||
{
|
||||
rc = mosquitto_subscribe(client, nullptr, config.immichTopic.c_str(), config.qos);
|
||||
if (rc != MOSQ_ERR_SUCCESS)
|
||||
{
|
||||
Log("MQTT subscribe (immich) failed: ", mosquitto_strerror(rc));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("MQTT subscribed to ", config.immichTopic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MqttController::HandleMessage(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
|
||||
@@ -113,15 +127,26 @@ void MqttController::HandleMessage(struct mosquitto *mosq, void *userdata, const
|
||||
return;
|
||||
|
||||
QString payload = QString::fromUtf8(static_cast<const char *>(message->payload), message->payloadlen);
|
||||
QMetaObject::invokeMethod(self, "handleCommand", Qt::QueuedConnection, Q_ARG(QString, payload));
|
||||
QString topic = QString::fromUtf8(message->topic);
|
||||
QMetaObject::invokeMethod(self, "handleMessage", Qt::QueuedConnection,
|
||||
Q_ARG(QString, topic),
|
||||
Q_ARG(QString, payload));
|
||||
}
|
||||
|
||||
void MqttController::handleCommand(const QString &payload)
|
||||
void MqttController::handleMessage(const QString &topic, const QString &payload)
|
||||
{
|
||||
QString cmd = payload.trimmed().toLower();
|
||||
if (cmd.isEmpty())
|
||||
return;
|
||||
|
||||
Log("MQTT message on ", topic.toStdString(), ": ", cmd.toStdString());
|
||||
|
||||
if (!immichTopic.isEmpty() && topic == immichTopic)
|
||||
{
|
||||
emit immichControl(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "play" || cmd == "resume")
|
||||
{
|
||||
emit play();
|
||||
|
||||
@@ -24,9 +24,10 @@ signals:
|
||||
void nextImage();
|
||||
void nextFolder();
|
||||
void restart();
|
||||
void immichControl(const QString &payload);
|
||||
|
||||
private slots:
|
||||
void handleCommand(const QString &payload);
|
||||
void handleMessage(const QString &topic, const QString &payload);
|
||||
|
||||
private:
|
||||
static void HandleConnect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
@@ -35,6 +36,8 @@ private:
|
||||
void subscribe();
|
||||
|
||||
MqttConfig config;
|
||||
QString controlTopic;
|
||||
QString immichTopic;
|
||||
struct mosquitto *client = nullptr;
|
||||
bool connected = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user