Add portable mode for Windows

Detect portable marker file (portable or portable.txt) next to exe
and store data/cache in app directory instead of AppData.

ZIP package includes marker file for out-of-box portable use.
This commit is contained in:
Andrew Rabert committed 2025-12-26 03:51:15 -05:00
1 parent 747275054e
commit 3e840b3c8c
4 files changed
+107 -2

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# Prepare portable ZIP contents
# Copies installed files to a separate directory with runtime DLLs in root
set(SOURCE_DIR "@CMAKE_INSTALL_PREFIX@")
set(ZIP_DIR "@CMAKE_CURRENT_BINARY_DIR@/portable")
set(PROJECT_SOURCE_DIR "@PROJECT_SOURCE_DIR@")
# Clean and create ZIP staging directory
file(REMOVE_RECURSE "${ZIP_DIR}")
file(MAKE_DIRECTORY "${ZIP_DIR}")
# Copy all files except redist folder and vc_redist installer
file(GLOB_RECURSE ALL_FILES RELATIVE "${SOURCE_DIR}" "${SOURCE_DIR}/*")
foreach(f ${ALL_FILES})
# Skip redist folder and VCRedist installer
if(f MATCHES "^redist/" OR f STREQUAL "vc_redist.x64.exe")
continue()
endif()
get_filename_component(dir "${f}" DIRECTORY)
if(dir)
file(MAKE_DIRECTORY "${ZIP_DIR}/${dir}")
endif()
file(COPY "${SOURCE_DIR}/${f}" DESTINATION "${ZIP_DIR}/${dir}")
endforeach()
# Copy runtime DLLs to root (for portable use)
set(VCRUNTIME_DIR "${PROJECT_SOURCE_DIR}/deps/vcruntime")
if(EXISTS "${VCRUNTIME_DIR}")
file(GLOB RUNTIME_DLLS "${VCRUNTIME_DIR}/*.dll")
foreach(dll ${RUNTIME_DLLS})
file(COPY "${dll}" DESTINATION "${ZIP_DIR}")
endforeach()
message(STATUS "Bundled VC runtime DLLs for portable package")
endif()
# Create portable marker file
file(WRITE "${ZIP_DIR}/portable" "")
message(STATUS "Created portable marker file")
message(STATUS "Portable package prepared in: ${ZIP_DIR}")
+9 -1
View File
@@ -193,7 +193,15 @@ int main(int argc, char *argv[])
return EXIT_SUCCESS;
}
// Handle config-dir first (affects where profiles.json is located)
// Check for portable mode (marker file next to exe)
if (Paths::detectAndEnablePortableMode())
{
// Portable mode sets its own paths, skip config-dir
QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope,
Paths::globalDataDir());
}
// Handle config-dir (overrides portable mode if explicitly set)
auto configDir = parser.value("config-dir");
if (!configDir.isEmpty())
{
+54 -1
View File
@@ -15,7 +15,9 @@
/////////////////////////////////////////////////////////////////////////////////////////
static QString g_configDirOverride;
static QString g_cacheDirOverride;
static QString g_activeProfileId;
static bool g_portableMode = false;
/////////////////////////////////////////////////////////////////////////////////////////
void Paths::setConfigDir(const QString& path)
@@ -23,6 +25,42 @@ void Paths::setConfigDir(const QString& path)
g_configDirOverride = path;
}
/////////////////////////////////////////////////////////////////////////////////////////
void Paths::setCacheDir(const QString& path)
{
g_cacheDirOverride = path;
}
/////////////////////////////////////////////////////////////////////////////////////////
bool Paths::isPortableMode()
{
return g_portableMode;
}
/////////////////////////////////////////////////////////////////////////////////////////
bool Paths::detectAndEnablePortableMode()
{
#ifdef Q_OS_WIN
QString appDir = QGuiApplication::applicationDirPath();
// Check for portable marker file
if (QFile::exists(appDir + "/portable") || QFile::exists(appDir + "/portable.txt"))
{
QString dataPath = appDir + "/data";
QString cachePath = appDir + "/cache";
g_portableMode = true;
g_configDirOverride = dataPath;
g_cacheDirOverride = cachePath;
qInfo() << "Portable mode enabled, data:" << dataPath;
return true;
}
#endif
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////
static QDir writableLocation(QStandardPaths::StandardLocation loc)
{
@@ -93,7 +131,22 @@ QString Paths::globalDataDir(const QString& file)
/////////////////////////////////////////////////////////////////////////////////////////
QString Paths::globalCacheDir(const QString& file)
{
QDir d = writableLocation(QStandardPaths::GenericCacheLocation);
QDir d;
if (!g_cacheDirOverride.isEmpty())
{
d = QDir(g_cacheDirOverride);
if (!d.mkpath(d.absolutePath()))
{
qWarning() << "Failed to create cache override directory:" << d.absolutePath();
d = writableLocation(QStandardPaths::GenericCacheLocation);
}
}
else
{
d = writableLocation(QStandardPaths::GenericCacheLocation);
}
if (file.isEmpty())
return d.absolutePath();
return d.filePath(file);
+3
View File
@@ -12,6 +12,9 @@
namespace Paths
{
void setConfigDir(const QString& path);
void setCacheDir(const QString& path);
bool isPortableMode();
bool detectAndEnablePortableMode();
QString resourceDir(const QString& file = QString());
QString socketName(const QString& serverName);
QString soundsPath(const QString& sound);