feat: cli options

This commit is contained in:
2026-01-15 03:56:26 +01:00
parent 92ba8738fe
commit f71273a7df
10 changed files with 268 additions and 168 deletions
+1
View File
@@ -1,5 +1,6 @@
.cache
.vscode
.qtcreator
build-dbg
build
*.log
+14 -10
View File
@@ -1,6 +1,10 @@
cmake_minimum_required(VERSION 3.16)
project(wallpaper_carousel VERSION 0.1 LANGUAGES CXX)
project(Wallpaper_Carousel VERSION 1.0.0 LANGUAGES CXX)
set(EXECUTABLE_NAME "wallpaper-carousel")
configure_file(src/version.h.in ${CMAKE_BINARY_DIR}/generated/version.h)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
@@ -27,7 +31,7 @@ set(PROJECT_SOURCES
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(wallpaper-carousel
qt_add_executable(${EXECUTABLE_NAME}
MANUAL_FINALIZATION
${PROJECT_SOURCES}
src/utils.h
@@ -44,22 +48,22 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(wallpaper-carousel SHARED
add_library(${EXECUTABLE_NAME} SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(wallpaper-carousel
add_executable(${EXECUTABLE_NAME}
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(wallpaper-carousel PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_link_libraries(${EXECUTABLE_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_include_directories(wallpaper-carousel PRIVATE src)
target_include_directories(${EXECUTABLE_NAME} PRIVATE src ${CMAKE_BINARY_DIR}/generated)
# if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
# target_compile_definitions(wallpaper_chooser PRIVATE
@@ -74,7 +78,7 @@ if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.wallpaper_chooser)
endif()
set_target_properties(wallpaper-carousel PROPERTIES
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
@@ -83,16 +87,16 @@ set_target_properties(wallpaper-carousel PROPERTIES
)
include(GNUInstallDirs)
install(TARGETS wallpaper-carousel
install(TARGETS ${EXECUTABLE_NAME}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES ${CMAKE_CURRENT_LIST_DIR}/app/wallpaper-carousel.desktop
install(FILES ${CMAKE_CURRENT_LIST_DIR}/app/${EXECUTABLE_NAME}.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(wallpaper-carousel)
qt_finalize_executable(${EXECUTABLE_NAME})
endif()
+4 -9
View File
@@ -1,4 +1,4 @@
#!/bin/env bash
#!/usr/bin/env bash
set -euo pipefail
@@ -9,15 +9,10 @@ path="$(dirname "$(readlink -f "$0")")"
cmake -S "$path/.." -B "$path/../build" \
-DCMAKE_INSTALL_PREFIX="$prefix"
cmake --build "$path/../build"
cmake --build "$path/../build" --config Release -- -j"$(nproc)"
if [ ! -w "$prefix" ] && [ "$(id -u)" -ne 0 ]; then
sudo cmake --install "$path/../build"
sudo cmake --install "$path/../build" --config Release
else
cmake --install "$path/../build"
fi
if command -v update-desktop-database &>/dev/null; then
echo "Updating desktop database..."
update-desktop-database "$prefix"/share/applications/ || true
cmake --install "$path/../build" --config Release
fi
+26 -76
View File
@@ -1,7 +1,7 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-08-05 01:34:52
* @LastEditTime: 2026-01-15 00:48:36
* @LastEditTime: 2026-01-15 03:54:42
* @Description: Configuration manager.
*/
#include "config.h"
@@ -15,19 +15,27 @@
#include <QStandardPaths>
#include "logger.h"
#include "utils.h"
using namespace GeneralLogger;
static QString expandPath(const QString& path);
const QString Config::s_DefaultConfigFileName = "config.json";
Config::Config(const QString& configDir, const QStringList& searchDirs, QObject* parent)
Config::Config(
const QString& configDir,
const QStringList& searchDirs,
const QString& configPath,
QObject* parent)
: QObject(parent), m_configDir(configDir) {
debug(QString("Loading configuration from: %1 ...").arg(configDir));
_loadConfig(configDir + QDir::separator() + s_DefaultConfigFileName);
debug(QString("Additional search directories: %1").arg(searchDirs.join(", ")));
m_wallpaperConfig.dirs.append(searchDirs);
if (configPath.isEmpty()) {
info(QString("Configuration directory: %1").arg(configDir));
_loadConfig(configDir + QDir::separator() + s_DefaultConfigFileName);
} else {
_loadConfig(configPath);
}
if (!searchDirs.isEmpty()) {
info(QString("Additional search directories: %1").arg(searchDirs.join(", ")));
m_wallpaperConfig.dirs.append(searchDirs);
}
debug("Loading wallpapers ...");
_loadWallpapers();
@@ -37,6 +45,7 @@ Config::~Config() {
}
void Config::_loadConfig(const QString& configPath) {
info(QString("Loading configuration from: %1").arg(configPath));
QFile configFile(configPath);
if (!configFile.open(QIODevice::ReadOnly)) {
critical(QString("Failed to open config file: %1").arg(configPath));
@@ -158,17 +167,16 @@ void Config::_loadConfig(const QString& configPath) {
if (currentObj.contains(pathParts[i]) && currentObj[pathParts[i]].isObject()) {
currentObj = currentObj[pathParts[i]].toObject();
} else {
warn(QString("Path '%1' not found").arg(pathParts.mid(0, i + 1).join('.')));
debug(QString("Path '%1' not found").arg(pathParts.mid(0, i + 1).join('.')));
return;
}
}
// 获取目标值
const QString& finalKey = pathParts.last();
if (currentObj.contains(finalKey)) {
mapping.parser(currentObj[finalKey]);
} else {
warn(QString("Key '%1' not found in '%2'").arg(finalKey).arg(mapping.path));
debug(QString("Key '%1' not found in '%2'").arg(finalKey).arg(mapping.path));
}
})();
}
@@ -187,11 +195,11 @@ void Config::_loadWallpapers() {
debug(QString("Loading wallpapers from %1 specified directories...").arg(m_wallpaperConfig.dirs.size()));
for (const QString& dirPath : m_wallpaperConfig.dirs) {
QDir dir(dirPath);
if (dir.exists()) {
if (checkDir(dirPath)) {
QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
for (const QString& file : files) {
QString filePath = dir.filePath(file);
paths.insert(filePath);
paths.insert(expandPath(filePath));
}
} else {
warn(QString("Directory '%1' does not exist").arg(dirPath));
@@ -205,70 +213,12 @@ void Config::_loadWallpapers() {
m_wallpapers.reserve(paths.size());
for (const QString& path : paths) {
if (isValidImageFile(path)) {
if (checkImageFile(path)) {
m_wallpapers.append(path);
} else {
warn(QString("File '%1' is not recognized as a valid image file").arg(path));
}
}
info(QString("Found %1 wallpapers").arg(paths.size()));
}
bool Config::isValidImageFile(const QString& filePath) {
static const QStringList validExtensions = {
".jpg",
".jpeg",
".jfif",
".png",
".bmp",
".gif",
".webp",
".tiff",
".avif",
".heic",
".heif"};
// check if exist
if (!QFile::exists(filePath)) {
warn(QString("File does not exist: %1").arg(filePath));
return false;
}
// check if normal file
QFileInfo fileInfo(filePath);
if (!(fileInfo.isFile() || fileInfo.isSymbolicLink()) || !fileInfo.isReadable()) {
warn(QString("Invalid file: %1").arg(filePath));
return false;
}
// check if valid extension
for (const QString& ext : validExtensions) {
if (filePath.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
warn(QString("Unsupported file type: %1").arg(filePath));
return false;
}
static QString expandPath(const QString& path) {
QString expandedPath = path;
if (expandedPath.startsWith("~/")) {
expandedPath.replace(0, 1, QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
} else if (expandedPath == "~") {
expandedPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
}
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QRegularExpression envVarRegex(R"(\$([A-Za-z_][A-Za-z0-9_]*))");
QRegularExpressionMatchIterator i = envVarRegex.globalMatch(expandedPath);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString varName = match.captured(1);
QString varValue = env.value(varName);
if (!varValue.isEmpty()) {
expandedPath.replace(match.captured(0), varValue);
}
}
return QDir::cleanPath(expandedPath);
info(QString("Found %1 files").arg(paths.size()));
}
+6 -4
View File
@@ -1,7 +1,7 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-08-05 01:34:52
* @LastEditTime: 2025-08-07 23:24:02
* @LastEditTime: 2026-01-15 03:40:25
* @Description: Configuration manager.
*/
#ifndef CONFIG_H
@@ -46,12 +46,14 @@ class Config : public QObject {
bool reverse = false;
};
Config(const QString& configDir, const QStringList& searchDirs = {}, QObject* parent = nullptr);
Config(
const QString& configDir,
const QStringList& searchDirs = {},
const QString& configPath = "",
QObject* parent = nullptr);
~Config();
static bool isValidImageFile(const QString& filePath);
[[nodiscard]] const QStringList& getWallpapers() const { return m_wallpapers; }
[[nodiscard]] qint64 getWallpaperCount() const { return m_wallpapers.size(); }
+1 -4
View File
@@ -1,15 +1,12 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-08-05 01:22:53
* @LastEditTime: 2026-01-14 23:26:32
* @LastEditTime: 2026-01-15 03:42:17
* @Description: Animated carousel widget for displaying and selecting images.
*/
#ifndef IMAGES_CAROUSEL_H
#define IMAGES_CAROUSEL_H
#include <qqueue.h>
#include <qtmetamacros.h>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMutex>
+29 -28
View File
@@ -1,7 +1,7 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-08-07 01:12:37
* @LastEditTime: 2026-01-15 02:10:15
* @LastEditTime: 2026-01-15 02:34:40
* @Description: Implementation of logger.
*/
#include "logger.h"
@@ -15,12 +15,13 @@
#include <QTextStream>
#include <cstdio>
Q_LOGGING_CATEGORY(logMain, "wallpaper.carousel")
#include "version.h"
static QTextStream* g_logStream = nullptr;
static bool g_isColored = false;
static QMutex g_logMutex;
static const QString g_appName = "wallpaper.carousel"; // same as above
Q_LOGGING_CATEGORY(logMain, APP_NAME)
static QTextStream* s_logStream = nullptr;
static bool s_isColored = false;
static QMutex s_logMutex;
static bool checkIsColored(FILE* stream) {
if (!stream || !isatty(fileno(stream))) {
@@ -37,51 +38,51 @@ static bool checkIsColored(FILE* stream) {
static void messageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
Q_UNUSED(context);
QMutexLocker locker(&g_logMutex);
QMutexLocker locker(&s_logMutex);
QString levelTag;
QString colorTag;
QString colorText;
QString resetColor = g_isColored ? "\033[0m" : "";
QString resetColor = s_isColored ? "\033[0m" : "";
switch (type) {
case QtDebugMsg:
levelTag = "[DEBUG]";
colorTag = g_isColored ? "\033[36m" : ""; // Cyan
colorTag = s_isColored ? "\033[36m" : ""; // Cyan
break;
case QtInfoMsg:
levelTag = "[INFO]";
colorTag = g_isColored ? "\033[92m" : ""; // Light Green
colorText = g_isColored ? "\033[32m" : ""; // Green
colorTag = s_isColored ? "\033[92m" : ""; // Light Green
colorText = s_isColored ? "\033[32m" : ""; // Green
break;
case QtWarningMsg:
levelTag = "[WARN]";
colorTag = g_isColored ? "\033[93m" : ""; // Light Yellow
colorText = g_isColored ? "\033[33m" : ""; // Yellow
colorTag = s_isColored ? "\033[93m" : ""; // Light Yellow
colorText = s_isColored ? "\033[33m" : ""; // Yellow
break;
case QtCriticalMsg:
levelTag = "[ERROR]";
colorTag = g_isColored ? "\033[91m" : ""; // Light Red
colorText = g_isColored ? "\033[31m" : ""; // Red
colorTag = s_isColored ? "\033[91m" : ""; // Light Red
colorText = s_isColored ? "\033[31m" : ""; // Red
break;
case QtFatalMsg:
levelTag = "[FATAL]";
colorTag = g_isColored ? "\033[95m" : ""; // Magenta
colorTag = s_isColored ? "\033[95m" : ""; // Magenta
break;
}
if (g_logStream) {
(*g_logStream) << colorTag << levelTag << " " << colorText << msg << resetColor << Qt::endl;
g_logStream->flush();
if (s_logStream) {
(*s_logStream) << colorTag << levelTag << " " << colorText << msg << resetColor << Qt::endl;
s_logStream->flush();
}
}
void Logger::init(FILE* stream) {
if (stream) {
delete g_logStream;
g_logStream = new QTextStream(stream);
delete s_logStream;
s_logStream = new QTextStream(stream);
}
g_isColored = checkIsColored(stream);
s_isColored = checkIsColored(stream);
qInstallMessageHandler(messageOutput);
}
@@ -89,25 +90,25 @@ void Logger::init(FILE* stream) {
void Logger::setLogLevel(QtMsgType level) {
switch (level) {
case QtDebugMsg:
QLoggingCategory::setFilterRules(QString("%1.debug=true").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=true").arg(APP_NAME));
break;
case QtInfoMsg:
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=true").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=true").arg(APP_NAME));
break;
case QtWarningMsg:
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=true").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=true").arg(APP_NAME));
break;
case QtCriticalMsg:
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=true").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=true").arg(APP_NAME));
break;
case QtFatalMsg:
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=false").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=false").arg(APP_NAME));
break;
}
}
void Logger::quiet() {
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=false\n%1.fatal=false").arg(g_appName));
QLoggingCategory::setFilterRules(QString("%1.debug=false\n%1.info=false\n%1.warning=false\n%1.critical=false\n%1.fatal=false").arg(APP_NAME));
}
void GeneralLogger::debug(const QString& msg) {
+111 -35
View File
@@ -1,13 +1,11 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-08-05 00:37:58
* @LastEditTime: 2026-01-15 01:43:14
* @Description: Entry point.
* @LastEditTime: 2026-01-15 03:41:55
* @Description: Argument parser and entry point.
*/
#include <qapplication.h>
#include <qlogging.h>
#include <QApplication>
#include <QCommandLineParser>
#include <QDir>
#include <QStandardPaths>
#include <QTextStream>
@@ -15,49 +13,127 @@
#include "config.h"
#include "logger.h"
#include "main_window.h"
#include "utils.h"
#include "version.h"
static class AppOptions {
QCommandLineParser parser{};
void printVersion() {
QTextStream out(stdout);
out << APP_NAME << " version " << APP_VERSION << Qt::endl;
doReturn = true;
}
void printHelp() {
QTextStream out(stdout);
out << parser.helpText() << Qt::endl;
doReturn = true;
}
void printError() {
if (!errorText.isEmpty()) {
QTextStream out(stderr);
out << errorText << Qt::endl;
printHelp();
}
doReturn = true;
}
public:
QString configPath = "";
QStringList appendDirs;
QString errorText = "";
bool doReturn = false;
void parseArgs(QApplication* a) {
parser.setApplicationDescription("A small wallpaper utility made with Qt");
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
QCommandLineOption verboseOption(QStringList() << "V" << "verbose", "Set log level to DEBUG");
parser.addOption(verboseOption);
QCommandLineOption quietOption(QStringList() << "q" << "quiet", "Suppress all log output");
parser.addOption(quietOption);
QCommandLineOption appendDirOption(QStringList() << "d" << "append-dir", "Append an additional wallpaper search directory", "dir");
parser.addOption(appendDirOption);
QCommandLineOption configFileOption(QStringList() << "c" << "config-file", "Specify a custom configuration file", "file");
parser.addOption(configFileOption);
if (!parser.parse(a->arguments())) {
errorText = parser.errorText();
doReturn = true;
return;
}
if (parser.isSet(versionOption)) {
printVersion();
return;
}
if (parser.isSet(helpOption)) {
printHelp();
return;
}
if (parser.isSet(verboseOption)) {
Logger::setLogLevel(QtDebugMsg);
} else if (parser.isSet(quietOption)) {
Logger::quiet();
} else {
Logger::setLogLevel(QtInfoMsg);
}
for (const QString& dir : parser.values(appendDirOption)) {
if (checkDir(dir)) {
appendDirs.append(dir);
} else {
errorText = QString("Error: Directory does not exist or is not accessible: %1").arg(dir);
printError();
return;
}
}
if (parser.isSet(configFileOption)) {
QString path = parser.value(configFileOption);
if (checkFile(path)) {
configPath = path;
} else {
errorText = QString("Error: Config file does not exist or is not accessible: %1").arg(path);
printError();
return;
}
}
}
} s_options;
static QString getConfigDir() {
auto configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
if (configDir.isEmpty()) {
configDir = QDir::homePath() + QDir::separator() + ".config" + QDir::separator() + "wallpaper_chooser";
configDir = QDir::homePath() + QDir::separator() + ".config" + QDir::separator() + APP_NAME;
}
QDir().mkpath(configDir);
return configDir;
}
static void setLogLevel(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
QString arg(argv[i]);
if (arg == "--debug" || arg == "-vvv" || arg == "--verbose") {
Logger::setLogLevel(QtDebugMsg);
return;
} else if (arg == "--info" || arg == "-vv") {
Logger::setLogLevel(QtInfoMsg);
return;
} else if (arg == "--warn" || arg == "-v") {
Logger::setLogLevel(QtWarningMsg);
return;
} else if (arg == "--critical") {
Logger::setLogLevel(QtCriticalMsg);
return;
} else if (arg == "--fatal") {
Logger::setLogLevel(QtFatalMsg);
return;
} else if (arg == "--quiet") {
Logger::quiet();
return;
}
}
Logger::setLogLevel(QtInfoMsg);
}
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
a.setApplicationName(APP_NAME);
a.setApplicationVersion(APP_VERSION);
Logger::init(stderr);
setLogLevel(argc, argv);
Logger::init();
s_options.parseArgs(&a);
Config config(getConfigDir());
if (s_options.doReturn) {
return s_options.errorText.isEmpty() ? 0 : 1;
}
Config config(getConfigDir(), s_options.appendDirs, s_options.configPath, &a);
MainWindow w(config);
w.show();
+73 -1
View File
@@ -1,13 +1,18 @@
/*
* @Author: Uyanide pywang0608@foxmail.com
* @Date: 2025-11-30 20:59:57
* @LastEditTime: 2025-12-07 06:08:18
* @LastEditTime: 2026-01-15 03:11:08
* @Description: THE utils header that every project needs :)
*/
#ifndef UTILS_H
#define UTILS_H
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
#include <utility>
template <typename Callable>
@@ -26,4 +31,71 @@ class Defer {
}
};
inline bool checkFile(const QString& path) {
QFileInfo checkFile(path);
return checkFile.exists() && checkFile.isFile() && checkFile.isReadable();
}
inline bool checkDir(const QString& path) {
QFileInfo checkFile(path);
return checkFile.exists() && checkFile.isDir() && checkFile.isReadable();
}
inline QString expandPath(const QString& path) {
QString expandedPath = path;
if (expandedPath.startsWith("~/")) {
expandedPath.replace(0, 1, QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
} else if (expandedPath == "~") {
expandedPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
}
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QRegularExpression envVarRegex(R"(\$([A-Za-z_][A-Za-z0-9_]*))");
QRegularExpressionMatchIterator i = envVarRegex.globalMatch(expandedPath);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString varName = match.captured(1);
QString varValue = env.value(varName);
if (!varValue.isEmpty()) {
expandedPath.replace(match.captured(0), varValue);
}
}
return QDir::cleanPath(expandedPath);
}
inline bool checkImageFile(const QString& filePath) {
static const QStringList validExtensions = {
".jpg",
".jpeg",
".jfif",
".png",
".bmp",
".gif",
".webp",
".tiff",
".avif",
".heic",
".heif"};
// check if exist
if (!QFile::exists(filePath)) {
return false;
}
// check if normal file
QFileInfo fileInfo(filePath);
if (!(fileInfo.isFile() || fileInfo.isSymbolicLink()) || !fileInfo.isReadable()) {
return false;
}
// check if valid extension
for (const QString& ext : validExtensions) {
if (filePath.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
#endif // UTILS_H
+2
View File
@@ -0,0 +1,2 @@
#define APP_VERSION "@PROJECT_VERSION@"
#define APP_NAME "@EXECUTABLE_NAME@"