- Add support for loading configuration via a file rather than the command line

- Added "-c" command line option to look for config file "slide.options.json" in. Otherwise looks in ~/.config/slide/slide.options.json or /etc/slide/slide.options.json
- Added code to reload config options at runtime (when the image is scheduled to update)
This commit is contained in:
Alfred Reynolds
2021-08-10 18:20:33 +12:00
parent 2e96ea4814
commit 8ac20f4b43
3 changed files with 261 additions and 76 deletions

View File

@@ -9,10 +9,10 @@
#include <stdlib.h> /* srand, rand */ #include <stdlib.h> /* srand, rand */
#include <time.h> /* time */ #include <time.h> /* time */
ImageSwitcher::ImageSwitcher(MainWindow& w, unsigned int timeout, std::unique_ptr<ImageSelector>& selector): ImageSwitcher::ImageSwitcher(MainWindow& w, unsigned int timeoutMsec, std::shared_ptr<ImageSelector>& selector):
QObject::QObject(), QObject::QObject(),
window(w), window(w),
timeout(timeout), timeout(timeoutMsec),
selector(selector), selector(selector),
timer(this), timer(this),
timerNoContent(this) timerNoContent(this)
@@ -21,6 +21,10 @@ ImageSwitcher::ImageSwitcher(MainWindow& w, unsigned int timeout, std::unique_pt
void ImageSwitcher::updateImage() void ImageSwitcher::updateImage()
{ {
if(reloadConfigIfNeeded)
{
reloadConfigIfNeeded();
}
ImageDetails imageDetails = selector->getNextImage(window.getBaseOptions()); ImageDetails imageDetails = selector->getNextImage(window.getBaseOptions());
if (imageDetails.filename == "") if (imageDetails.filename == "")
{ {
@@ -47,3 +51,14 @@ void ImageSwitcher::scheduleImageUpdate()
// update our image in 100msec, to let the system settle // update our image in 100msec, to let the system settle
QTimer::singleShot(100, this, SLOT(updateImage())); QTimer::singleShot(100, this, SLOT(updateImage()));
} }
void ImageSwitcher::setConfigFileReloader(std::function<void()> reloadConfigIfNeededIn)
{
reloadConfigIfNeeded = reloadConfigIfNeededIn;
}
void ImageSwitcher::setRotationTime(unsigned int timeoutMsecIn)
{
timeout = timeoutMsecIn;
timer.start(timeout);
}

View File

@@ -5,6 +5,7 @@
#include <QTimer> #include <QTimer>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <functional>
class MainWindow; class MainWindow;
class ImageSelector; class ImageSelector;
@@ -12,19 +13,22 @@ class ImageSwitcher : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ImageSwitcher(MainWindow& w, unsigned int timeout, std::unique_ptr<ImageSelector>& selector); ImageSwitcher(MainWindow& w, unsigned int timeoutMsec, std::shared_ptr<ImageSelector>& selector);
void start(); void start();
void scheduleImageUpdate(); void scheduleImageUpdate();
void setConfigFileReloader(std::function<void()> reloadConfigIfNeededIn);
void setRotationTime(unsigned int timeoutMsec);
public slots: public slots:
void updateImage(); void updateImage();
private: private:
MainWindow& window; MainWindow& window;
unsigned int timeout; unsigned int timeout;
std::unique_ptr<ImageSelector>& selector; std::shared_ptr<ImageSelector>& selector;
QTimer timer; QTimer timer;
const unsigned int timeoutNoContent = 5 * 1000; // 5 sec const unsigned int timeoutNoContent = 5 * 1000; // 5 sec
QTimer timerNoContent; QTimer timerNoContent;
std::function<void()> reloadConfigIfNeeded;
}; };
#endif // IMAGESWITCHER_H #endif // IMAGESWITCHER_H

View File

@@ -4,6 +4,11 @@
#include "pathtraverser.h" #include "pathtraverser.h"
#include "overlay.h" #include "overlay.h"
#include <QApplication> #include <QApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QDateTime>
#include <QFileInfo>
#include <iostream> #include <iostream>
#include <sys/file.h> #include <sys/file.h>
#include <errno.h> #include <errno.h>
@@ -15,18 +20,15 @@
#include <memory> #include <memory>
void usage(std::string programName) { void usage(std::string programName) {
std::cerr << "Usage: " << programName << " [-t rotation_seconds] [-a aspect('l','p','a')] [-o background_opacity(0..255)] [-b blur_radius] -p image_folder [-r] [-s] [-v] [--verbose] [--stretch]" << std::endl; std::cerr << "Usage: " << programName << " [-t rotation_seconds] [-a aspect('l','p','a', 'm')] [-o background_opacity(0..255)] [-b blur_radius] -p image_folder [-r] [-s] [-v] [--verbose] [--stretch] [-c config_file_path]" << std::endl;
} }
int main(int argc, char *argv[]) struct Config {
{
unsigned int rotationSeconds = 30;
std::string path = ""; std::string path = "";
std::string configPath = "";
QApplication a(argc, argv); unsigned int rotationSeconds = 30;
int blurRadius = -1;
MainWindow w; int backgroundOpacity = -1;
int opt;
bool recursive = false; bool recursive = false;
bool shuffle = false; bool shuffle = false;
bool sorted = false; bool sorted = false;
@@ -35,6 +37,134 @@ int main(int argc, char *argv[])
std::string valid_aspects = "alpm"; // all, landscape, portait std::string valid_aspects = "alpm"; // all, landscape, portait
std::string overlay = ""; std::string overlay = "";
std::string imageList = ""; // comma delimited list of images to show std::string imageList = ""; // comma delimited list of images to show
QDateTime loadTime;
};
ImageAspect parseAspectFromString(char aspect) {
switch(aspect)
{
case 'l':
return ImageAspect_Landscape;
break;
case 'p':
return ImageAspect_Portrait;
break;
case 'm':
return ImageAspect_Monitor;
break;
default:
case 'a':
return ImageAspect_Any;
break;
}
}
std::string ParseJSONString(QJsonObject jsonDoc, const char *key) {
if(jsonDoc.contains(key) && jsonDoc[key].isString())
{
return jsonDoc[key].toString().toStdString();
}
return "";
}
void SetJSONBool(bool &value, QJsonObject jsonDoc, const char *key) {
if(jsonDoc.contains(key) && jsonDoc[key].isBool())
{
value = jsonDoc[key].toBool();
}
}
QString getConfigFilePath(const std::string &configPath) {
std::string userConfigFolder = "~/.config/slide/";
std::string systemConfigFolder = "/etc/slide";
QString baseConfigFilename("slide.options.json");
QDir directory(userConfigFolder.c_str());
QString jsonFile = "";
if (!configPath.empty())
{
directory.setPath(configPath.c_str());
jsonFile = directory.filePath(baseConfigFilename);
}
if(!directory.exists(jsonFile))
{
directory.setPath(userConfigFolder.c_str());
jsonFile = directory.filePath(baseConfigFilename);
}
if(!directory.exists(jsonFile))
{
directory.setPath(systemConfigFolder.c_str());
jsonFile = directory.filePath(baseConfigFilename);
}
if(directory.exists(jsonFile))
{
return jsonFile;
}
return "";
}
Config loadConfiguration(const Config &commandLineConfig) {
QString jsonFile = getConfigFilePath(commandLineConfig.configPath);
QDir directory;
if(!directory.exists(jsonFile))
{
return commandLineConfig; // nothing to load
}
Config userConfig = commandLineConfig;
if(userConfig.debugMode)
{
std::cout << "Found options file: " << jsonFile.toStdString() << std::endl;
}
QString val;
QFile file;
file.setFileName(jsonFile);
file.open(QIODevice::ReadOnly | QIODevice::Text);
val = file.readAll();
file.close();
QJsonDocument d = QJsonDocument::fromJson(val.toUtf8());
QJsonObject jsonDoc = d.object();
SetJSONBool(userConfig.baseDisplayOptions.fitAspectAxisToWindow, jsonDoc, "fitAspectAxisToWindow");
SetJSONBool(userConfig.recursive, jsonDoc, "recursive");
SetJSONBool(userConfig.shuffle, jsonDoc, "shuffle");
SetJSONBool(userConfig.debugMode, jsonDoc, "debug");
std::string aspectString = ParseJSONString(jsonDoc, "aspect");
if(!aspectString.empty())
{
userConfig.baseDisplayOptions.onlyAspect = parseAspectFromString(aspectString[0]);
}
if(jsonDoc.contains("rotationSeconds") && jsonDoc["rotationSeconds"].isDouble())
{
userConfig.rotationSeconds = (int)jsonDoc["rotationSeconds"].toDouble();
}
if(jsonDoc.contains("opacity") && jsonDoc["opacity"].isDouble())
{
userConfig.backgroundOpacity = (int)jsonDoc["opacity"].toDouble();
}
std::string overlayString = ParseJSONString(jsonDoc, "overlay");
if(!overlayString.empty())
{
userConfig.overlay = overlayString;
}
std::string pathString = ParseJSONString(jsonDoc, "path");
if(!pathString.empty())
{
userConfig.path = pathString;
}
userConfig.loadTime = QDateTime::currentDateTime();
return userConfig;
}
bool parseCommandLine(Config &appConfig, int argc, char *argv[]) {
int opt;
int debugInt = 0; int debugInt = 0;
int stretchInt = 0; int stretchInt = 0;
static struct option long_options[] = static struct option long_options[] =
@@ -43,135 +173,171 @@ int main(int argc, char *argv[])
{"stretch", no_argument, &stretchInt, 1}, {"stretch", no_argument, &stretchInt, 1},
}; };
int option_index = 0; int option_index = 0;
while ((opt = getopt_long(argc, argv, "b:p:t:o:O:a:i:rsSv", long_options, &option_index)) != -1) { while ((opt = getopt_long(argc, argv, "b:p:t:o:O:a:i:c:rsSv", long_options, &option_index)) != -1) {
switch (opt) { switch (opt) {
case 0: case 0:
/* If this option set a flag, do nothing else now. */ /* If this option set a flag, do nothing else now. */
if (long_options[option_index].flag != 0) if (long_options[option_index].flag != 0)
break; break;
usage(argv[0]); return false;
return 1;
break; break;
case 'p': case 'p':
path = optarg; appConfig.path = optarg;
break; break;
case 'a': case 'a':
if ( valid_aspects.find(optarg[0]) == std::string::npos ) if (appConfig.valid_aspects.find(optarg[0]) == std::string::npos)
{ {
std::cout << "Invalid Aspect option, defaulting to all" << std::endl; std::cout << "Invalid Aspect option, defaulting to all" << std::endl;
baseDisplayOptions.onlyAspect = ImageAspect_Any; appConfig.baseDisplayOptions.onlyAspect = ImageAspect_Any;
} }
else else
{ {
switch(optarg[0]) appConfig.baseDisplayOptions.onlyAspect = parseAspectFromString(optarg[0]);
{
case 'l':
baseDisplayOptions.onlyAspect = ImageAspect_Landscape;
break;
case 'p':
baseDisplayOptions.onlyAspect = ImageAspect_Portrait;
break;
case 'm':
baseDisplayOptions.onlyAspect = ImageAspect_Monitor;
break;
default:
case 'a':
baseDisplayOptions.onlyAspect = ImageAspect_Any;
break;
}
} }
break; break;
case 't': case 't':
rotationSeconds = atoi(optarg); appConfig.rotationSeconds = atoi(optarg);
break; break;
case 'b': case 'b':
w.setBlurRadius(atoi(optarg)); appConfig.blurRadius = atoi(optarg);
break; break;
case 'o': case 'o':
w.setBackgroundOpacity(atoi(optarg)); appConfig.backgroundOpacity = atoi(optarg);
break; break;
case 'r': case 'r':
recursive = true; appConfig.recursive = true;
break; break;
case 's': case 's':
shuffle = true; appConfig.shuffle = true;
std::cout << "Shuffle mode is on." << std::endl; std::cout << "Shuffle mode is on." << std::endl;
break; break;
case 'S': case 'S':
sorted = true; appConfig.sorted = true;
break; break;
case 'O': case 'O':
overlay = optarg; appConfig.overlay = optarg;
break; break;
case 'v': case 'v':
debugMode = true; appConfig.debugMode = true;
break; break;
case 'i': case 'i':
imageList = optarg; appConfig.imageList = optarg;
break;
case 'c':
appConfig.configPath = optarg;
break; break;
default: /* '?' */ default: /* '?' */
usage(argv[0]); return false;
return 1;
} }
} }
if(debugInt==1) if(debugInt==1)
{ {
debugMode = true; appConfig.debugMode = true;
} }
if(stretchInt==1) if(stretchInt==1)
{ {
baseDisplayOptions.fitAspectAxisToWindow = true; appConfig.baseDisplayOptions.fitAspectAxisToWindow = true;
} }
if (path.empty() && imageList.empty()) return true;
}
void ReloadConfigIfNeeded(Config &appConfig, MainWindow &w, ImageSwitcher &switcher, std::shared_ptr<ImageSelector> &selector)
{
QString jsonFile = getConfigFilePath(appConfig.configPath);
QDir directory;
if(!directory.exists(jsonFile))
{
return;
}
if(appConfig.loadTime < QFileInfo(jsonFile).lastModified())
{
appConfig = loadConfiguration(appConfig);
w.setBaseOptions(appConfig.baseDisplayOptions);
w.setDebugMode(appConfig.debugMode);
selector->setDebugMode(appConfig.debugMode);
//Overlay o(appConfig.overlay);
//w.setOverlay(&o);
switcher.setRotationTime(appConfig.rotationSeconds * 1000);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
Config commandLineAppConfig;
if (!parseCommandLine(commandLineAppConfig, argc, argv))
{
usage(argv[0]);
return 1;
}
Config appConfig = loadConfiguration(commandLineAppConfig);
if (appConfig.path.empty() && appConfig.imageList.empty())
{ {
std::cout << "Error: Path expected." << std::endl; std::cout << "Error: Path expected." << std::endl;
usage(argv[0]); usage(argv[0]);
return 1; return 1;
} }
std::unique_ptr<PathTraverser> pathTraverser; if (appConfig.blurRadius>= 0)
if (!imageList.empty())
{ {
pathTraverser = std::unique_ptr<PathTraverser>(new ImageListPathTraverser(imageList, debugMode)); w.setBlurRadius(appConfig.blurRadius);
}
else if (recursive)
{
pathTraverser = std::unique_ptr<PathTraverser>(new RecursivePathTraverser(path, debugMode));
}
else
{
pathTraverser = std::unique_ptr<PathTraverser>(new DefaultPathTraverser(path, debugMode));
} }
std::unique_ptr<ImageSelector> selector; if (appConfig.backgroundOpacity>= 0)
if (sorted)
{ {
selector = std::unique_ptr<ImageSelector>(new SortedImageSelector(pathTraverser)); w.setBackgroundOpacity(appConfig.backgroundOpacity);
} }
else if (shuffle)
std::unique_ptr<PathTraverser> pathTraverser;
if (!appConfig.imageList.empty())
{ {
selector = std::unique_ptr<ImageSelector>(new ShuffleImageSelector(pathTraverser)); pathTraverser = std::unique_ptr<PathTraverser>(new ImageListPathTraverser(appConfig.imageList, appConfig.debugMode));
}
else if (appConfig.recursive)
{
pathTraverser = std::unique_ptr<PathTraverser>(new RecursivePathTraverser(appConfig.path, appConfig.debugMode));
} }
else else
{ {
selector = std::unique_ptr<ImageSelector>(new RandomImageSelector(pathTraverser)); pathTraverser = std::unique_ptr<PathTraverser>(new DefaultPathTraverser(appConfig.path, appConfig.debugMode));
} }
selector->setDebugMode(debugMode);
if(debugMode) std::shared_ptr<ImageSelector> selector;
if (appConfig.sorted)
{ {
std::cout << "Rotation Time: " << rotationSeconds << std::endl; selector = std::shared_ptr<ImageSelector>(new SortedImageSelector(pathTraverser));
std::cout << "Overlay input: " << overlay << std::endl;
} }
Overlay o(overlay); else if (appConfig.shuffle)
o.setDebugMode(debugMode); {
selector = std::shared_ptr<ImageSelector>(new ShuffleImageSelector(pathTraverser));
}
else
{
selector = std::shared_ptr<ImageSelector>(new RandomImageSelector(pathTraverser));
}
selector->setDebugMode(appConfig.debugMode);
if(appConfig.debugMode)
{
std::cout << "Rotation Time: " << appConfig.rotationSeconds << std::endl;
std::cout << "Overlay input: " << appConfig.overlay << std::endl;
}
Overlay o(appConfig.overlay);
o.setDebugMode(appConfig.debugMode);
w.setOverlay(&o); w.setOverlay(&o);
w.setDebugMode(debugMode); w.setBaseOptions(appConfig.baseDisplayOptions);
w.setBaseOptions(baseDisplayOptions);
w.show(); w.show();
ImageSwitcher switcher(w, rotationSeconds * 1000, selector); ImageSwitcher switcher(w, appConfig.rotationSeconds * 1000, selector);
w.setImageSwitcher(&switcher); w.setImageSwitcher(&switcher);
std::function<void()> reloader = [&appConfig, &w, &switcher, &selector]() { ReloadConfigIfNeeded(appConfig, w, switcher, selector); };
switcher.setConfigFileReloader(reloader);
switcher.start(); switcher.start();
return a.exec(); return a.exec();
} }