From f71273a7df120e0a1bcc11e221782345734aff77 Mon Sep 17 00:00:00 2001 From: Uyanide Date: Thu, 15 Jan 2026 03:56:26 +0100 Subject: [PATCH] feat: cli options --- .gitignore | 3 +- CMakeLists.txt | 24 ++++--- app/install.sh | 13 ++-- src/config.cpp | 102 ++++++++--------------------- src/config.h | 10 +-- src/images_carousel.h | 5 +- src/logger.cpp | 57 +++++++++-------- src/main.cpp | 146 ++++++++++++++++++++++++++++++++---------- src/utils.h | 74 ++++++++++++++++++++- src/version.h.in | 2 + 10 files changed, 268 insertions(+), 168 deletions(-) create mode 100644 src/version.h.in diff --git a/.gitignore b/.gitignore index 2757312..b7b45a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .cache .vscode +.qtcreator build-dbg build *.log -*.user \ No newline at end of file +*.user diff --git a/CMakeLists.txt b/CMakeLists.txt index c896950..f1e1c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/app/install.sh b/app/install.sh index 5a1e3a8..7a9d10c 100755 --- a/app/install.sh +++ b/app/install.sh @@ -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 diff --git a/src/config.cpp b/src/config.cpp index 6f26452..91403e0 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -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 #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())); } diff --git a/src/config.h b/src/config.h index a8a3d64..8d7f6ba 100644 --- a/src/config.h +++ b/src/config.h @@ -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(); } diff --git a/src/images_carousel.h b/src/images_carousel.h index a8ba6fe..d7ec102 100644 --- a/src/images_carousel.h +++ b/src/images_carousel.h @@ -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 -#include - #include #include #include diff --git a/src/logger.cpp b/src/logger.cpp index a239f03..ef93011 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -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 #include -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) { diff --git a/src/main.cpp b/src/main.cpp index 642bd8a..1a76fc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 -#include - #include +#include #include #include #include @@ -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(); diff --git a/src/utils.h b/src/utils.h index 31cc92e..22d0f9b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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 +#include +#include +#include +#include #include template @@ -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 diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000..fd899ab --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,2 @@ +#define APP_VERSION "@PROJECT_VERSION@" +#define APP_NAME "@EXECUTABLE_NAME@"