From 72deed70e838ccbbfa569b74070829d0aaf4634f Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Thu, 17 Sep 2026 17:01:17 +0200 Subject: [PATCH] Point the build and runtime paths at src/ and data/. Co-authored-by: Cursor --- .gitignore | 197 ++++----------------------------------- CMakeLists.txt | 29 +++--- Makefile | 58 ++++++------ README.md | 16 ++-- build-windows.bat | 9 +- data/windows/maturita.rc | 2 +- scripts/bundle-linux.sh | 12 +-- scripts/bundle-macos.sh | 8 +- scripts/gen-icons.py | 8 +- src/main.c | 1 + src/util.c | 54 +++++++++-- 11 files changed, 134 insertions(+), 260 deletions(-) diff --git a/.gitignore b/.gitignore index dfe1514..9e12c97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,184 +1,25 @@ -# ---> C -# Prerequisites -#.d +# Binaries and build trees +/maturita +/maturita.exe +/build/ +/dist/ +/progress/ +*.o +*.d +*.res +*.exe -#jetbrains slop -.idea/ +# Generated icon copies (source of truth is data/share/icons) +/assets/icons/ -# binary +# Editor / OS .DS_Store -sprechen -maturita +.idea/ +.vscode/ +cmake-build-*/ +*.dSYM/ + +# Local notes error.txt error.log CLAUDE.md -progress/ -dist/ -build/ - -# Generated from assets/app-icon.png (Makefile / CMake POST_BUILD) -assets/icons/ - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -# The rule above targets kernel build artifacts such as .foo.o.cmd; the -# Windows installer is a source file that has to be tracked. -!installers/*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -# debug information files -*.dwo - -# CLion -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#cmake-build-* - -# ---> VisualStudioCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets -!*.code-workspace - -# Built Visual Studio Code Extensions -*.vsix - -# ---> JetBrains -# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ -.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119 - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based HTTP Client -.idea/httpRequests -http-client.private.env.json - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -# Apifox Helper cache -.idea/.cache/.Apifox_Helper -.idea/ApifoxUploaderProjectSetting.xml - -# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215 -.idea/**/copilot.data.migration.*.xml - diff --git a/CMakeLists.txt b/CMakeLists.txt index 06fb532..199e233 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,6 @@ set(CMAKE_C_STANDARD_REQUIRED ON) # GTK4 is found through pkg-config everywhere: Linux/macOS packages, MSYS2, # vcpkg and the native MSVC (gvsbuild) package all ship .pc files. if(MSVC) - # On MSVC the Unix-style flags pkg-config prints are not understood, so - # ask for the native "/I" and ".lib" form instead. find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config pkg-config.exe pkgconf pkgconf.exe) if(NOT PKG_CONFIG_EXECUTABLE) @@ -42,17 +40,18 @@ else() pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4) endif() -# WIN32 suppresses the console window on Windows; the app still uses main(), -# so MSVC needs mainCRTStartup as the entry point. -file(GLOB MATURITA_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.c") +file(GLOB MATURITA_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c") if(WIN32) add_executable(maturita WIN32 ${MATURITA_SOURCES} - "${CMAKE_CURRENT_SOURCE_DIR}/maturita.rc") + "${CMAKE_CURRENT_SOURCE_DIR}/data/windows/maturita.rc") else() add_executable(maturita ${MATURITA_SOURCES}) endif() -# Mirrors COMMIT in the Makefile: bakes the source commit in for the updater. +target_include_directories(maturita PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src") + if(DEFINED APP_COMMIT AND NOT APP_COMMIT STREQUAL "") target_compile_definitions(maturita PRIVATE APP_COMMIT="${APP_COMMIT}") endif() @@ -72,22 +71,18 @@ else() endif() endif() -# The GTK stylesheet, window icon and Freedesktop share/ tree are loaded at -# runtime from the working directory (or next to the binary). +# Runtime assets sit next to the binary after install / cmake --build. +set(ICON_512 + "${CMAKE_CURRENT_SOURCE_DIR}/data/share/icons/hicolor/512x512/apps/maturita.png") add_custom_command(TARGET maturita POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/style.css + ${CMAKE_CURRENT_SOURCE_DIR}/data/style.css $/style.css COMMAND ${CMAKE_COMMAND} -E make_directory $/icons/hicolor/512x512/apps COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/assets/app-icon.png + ${ICON_512} $/icons/hicolor/512x512/apps/maturita.png - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/hicolor/512x512/apps - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/assets/app-icon.png - ${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/hicolor/512x512/apps/maturita.png COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_SOURCE_DIR}/share + ${CMAKE_CURRENT_SOURCE_DIR}/data/share $/share) diff --git a/Makefile b/Makefile index 5785932..ce75a1d 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,15 @@ ifeq ($(PKG_CONFIG),) PKG_CONFIG := pkg-config endif -# Use $(shell ...) instead of backticks so the flags are expanded by make -# itself; IDEs that import the Makefile via a dry run then see the real -# include paths. GTK_CFLAGS := $(shell $(PKG_CONFIG) --cflags gtk4) GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4) -CFLAGS = -Wall -Wextra -Wno-deprecated-declarations $(GTK_CFLAGS) +SRC_DIR := src +DATA_DIR := data +BUILD_DIR := build + +CFLAGS = -Wall -Wextra -Wno-deprecated-declarations -I$(SRC_DIR) \ + -MMD -MP $(GTK_CFLAGS) LIBS = $(GTK_LIBS) -lm # Published builds pass the commit they were made from (the CI workflow @@ -25,69 +27,67 @@ ifneq ($(COMMIT),) CFLAGS += -DAPP_COMMIT='"$(COMMIT)"' endif -# Windows (MSYS2 / MinGW): .exe suffix, no console window, embed .ico. ifeq ($(OS),Windows_NT) TARGET = maturita.exe LIBS += -mwindows WINDRES ?= windres -RC_OBJ = maturita_rc.o +RC_SRC = $(DATA_DIR)/windows/maturita.rc +RC_OBJ = $(BUILD_DIR)/maturita_rc.o UNAME_S := else TARGET = maturita +RC_SRC = RC_OBJ = UNAME_S := $(shell uname -s 2>/dev/null) -# macOS Dock icon is set via AppKit (see macos_dock.c). ifeq ($(UNAME_S),Darwin) LIBS += -framework AppKit -framework Foundation endif endif -SRC = $(wildcard *.c) -OBJ = $(SRC:.c=.o) +SRC = $(wildcard $(SRC_DIR)/*.c) +OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC)) +DEP = $(OBJ:.o=.d) -ICON_SRC = assets/app-icon.png -# Always use the real 512×512 asset — copying app-icon.png (1024×1024) breaks -# linuxdeploy, which rejects non-standard icon resolutions. -ICON_DST = share/icons/hicolor/512x512/apps/maturita.png -ICON_RUNTIME = assets/icons/hicolor/512x512/apps/maturita.png +ICON_DST = $(DATA_DIR)/share/icons/hicolor/512x512/apps/maturita.png ICON_ICO = assets/app-icon.ico all: $(TARGET) -$(ICON_RUNTIME): $(ICON_DST) - mkdir -p $(dir $@) - cp "$(ICON_DST)" "$@" +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< ifeq ($(OS),Windows_NT) -$(RC_OBJ): maturita.rc $(ICON_ICO) - $(WINDRES) maturita.rc -o $(RC_OBJ) +$(RC_OBJ): $(RC_SRC) $(ICON_ICO) | $(BUILD_DIR) + $(WINDRES) -I. -o $@ $< -$(TARGET): $(SRC) $(ICON_RUNTIME) $(RC_OBJ) - $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(RC_OBJ) $(LIBS) +$(TARGET): $(OBJ) $(RC_OBJ) + $(CC) $(CFLAGS) -o $@ $(OBJ) $(RC_OBJ) $(LIBS) else -$(TARGET): $(SRC) $(ICON_RUNTIME) - $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(LIBS) +$(TARGET): $(OBJ) + $(CC) $(CFLAGS) -o $@ $(OBJ) $(LIBS) endif clean: - rm -f $(TARGET) maturita.exe $(OBJ) $(RC_OBJ) maturita_rc.o + rm -f $(TARGET) maturita.exe + rm -rf $(BUILD_DIR) run: $(TARGET) ./$(TARGET) -# Self-contained package for the current platform into dist/. ifeq ($(OS),Windows_NT) bundle: $(TARGET) @rm -rf dist @mkdir -p dist @cp $(TARGET) dist/ - @cp style.css dist/ + @cp $(DATA_DIR)/style.css dist/ @mkdir -p dist/icons/hicolor/512x512/apps @cp "$(ICON_DST)" dist/icons/hicolor/512x512/apps/ - @cp -R share dist/ + @cp -R $(DATA_DIR)/share dist/ @ldd $(TARGET) | grep -Ei '/(ucrt64|mingw64)/bin/' | awk '{print $$3}' \ | xargs -r -I{} cp -f {} dist/ - @# GTK runtime data (schemas / pixbuf loaders) next to the exe. @GTK_PREFIX="$$($(PKG_CONFIG) --variable=prefix gtk4)"; \ if [ -d "$$GTK_PREFIX/share/glib-2.0" ]; then \ mkdir -p dist/share && cp -R "$$GTK_PREFIX/share/glib-2.0" dist/share/; \ @@ -106,4 +106,6 @@ bundle: @./scripts/bundle-linux.sh endif +-include $(DEP) + .PHONY: all clean run bundle diff --git a/README.md b/README.md index adc4eb9..627cef3 100644 --- a/README.md +++ b/README.md @@ -125,12 +125,12 @@ make bundle # dist/maturita.exe jde spustit i z Průzkumník ### Struktura ``` -*.c / maturita.h zdroj aplikace -style.css GTK styl (barvy se vkládají za běhu) +src/ C zdroje a maturita.h +data/ style.css, share/, podpisy, Windows .rc +assets/ zdrojová ikona a screenshoty installers/ instalátory, které stáhnou aktuální build scripts/ balení AppImage a .app -.github/workflows/build.yml CI: push na main → větev builds -assets/ ikona a screenshoty +.github/workflows/ CI: push na main → větev builds progress/ vzniká za běhu (cvičení, nastavení) ``` @@ -256,12 +256,12 @@ make bundle # dist/maturita.exe can be double-clicked ### Layout ``` -*.c / maturita.h application sources -style.css GTK stylesheet (palette injected at runtime) +src/ C sources and maturita.h +data/ style.css, share/, signing files, Windows .rc +assets/ source icon and screenshots installers/ fetch-the-latest installers scripts/ AppImage / .app bundlers -.github/workflows/build.yml CI: push to main → builds branch -assets/ icon and screenshots +.github/workflows/ CI: push to main → builds branch progress/ created at runtime (exercises, settings) ``` diff --git a/build-windows.bat b/build-windows.bat index 23ceb97..6fe2c98 100644 --- a/build-windows.bat +++ b/build-windows.bat @@ -75,15 +75,16 @@ if not exist "assets\app-icon.ico" ( REM --- 4. Compile resources + sources --------------------------------------- echo Building maturita.exe with MSVC... -rc /nologo /fo maturita.res maturita.rc +if not exist build mkdir build +rc /nologo /fo build\maturita.res data\windows\maturita.rc if errorlevel 1 ( echo [error] Resource compile failed. exit /b 1 ) -cl /nologo /O2 /MD /W3 /std:c11 /D_CRT_SECURE_NO_WARNINGS ^ - %GTK_CFLAGS% *.c maturita.res ^ - /Fe:maturita.exe ^ +cl /nologo /O2 /MD /W3 /std:c11 /I src /D_CRT_SECURE_NO_WARNINGS ^ + %GTK_CFLAGS% src\*.c build\maturita.res ^ + /Fe:maturita.exe /Fo:build\ ^ /link /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup %GTK_LIBS% if errorlevel 1 ( diff --git a/data/windows/maturita.rc b/data/windows/maturita.rc index e8c7905..2d67547 100644 --- a/data/windows/maturita.rc +++ b/data/windows/maturita.rc @@ -1,3 +1,3 @@ /* Windows application icon embedded into maturita.exe. * The first ICON resource becomes the Explorer / taskbar icon. */ -1 ICON "assets/app-icon.ico" +1 ICON "../../assets/app-icon.ico" diff --git a/scripts/bundle-linux.sh b/scripts/bundle-linux.sh index bf0cc12..709ef46 100755 --- a/scripts/bundle-linux.sh +++ b/scripts/bundle-linux.sh @@ -20,7 +20,7 @@ OUT_NAME="maturita-linux-${ARCH}.AppImage" APPDIR="$ROOT/dist/AppDir" TOOLS="$ROOT/dist/tools" # Real 512×512 PNG (assets/app-icon.png is 1024×1024 and linuxdeploy rejects it). -ICON_512="$ROOT/share/icons/hicolor/512x512/apps/maturita.png" +ICON_512="$ROOT/data/share/icons/hicolor/512x512/apps/maturita.png" echo "==> Building maturita" make -C "$ROOT" clean @@ -35,16 +35,16 @@ mkdir -p "$APPDIR/usr/bin" \ "$APPDIR/share" cp -f "$ROOT/maturita" "$APPDIR/usr/bin/maturita" -cp -f "$ROOT/style.css" "$APPDIR/style.css" +cp -f "$ROOT/data/style.css" "$APPDIR/style.css" cp -f "$ICON_512" "$APPDIR/icons/hicolor/512x512/apps/maturita.png" cp -f "$ICON_512" "$APPDIR/usr/share/icons/hicolor/512x512/apps/maturita.png" cp -f "$ICON_512" "$APPDIR/maturita.png" -cp -R "$ROOT/share/." "$APPDIR/share/" -cp -R "$ROOT/share/." "$APPDIR/usr/share/" -cp -f "$ROOT/share/applications/org.maturita.Maturita.desktop" \ +cp -R "$ROOT/data/share/." "$APPDIR/share/" +cp -R "$ROOT/data/share/." "$APPDIR/usr/share/" +cp -f "$ROOT/data/share/applications/org.maturita.Maturita.desktop" \ "$APPDIR/usr/share/applications/org.maturita.Maturita.desktop" # AppImage desktop entry must live at AppDir root and Exec= must be AppRun-compatible. -cp -f "$ROOT/share/applications/org.maturita.Maturita.desktop" \ +cp -f "$ROOT/data/share/applications/org.maturita.Maturita.desktop" \ "$APPDIR/org.maturita.Maturita.desktop" # linuxdeploy expects Icon= without path and a matching PNG at AppDir root. sed -i.bak 's|^Exec=.*|Exec=maturita|' "$APPDIR/org.maturita.Maturita.desktop" diff --git a/scripts/bundle-macos.sh b/scripts/bundle-macos.sh index 3e23f6f..41010b7 100755 --- a/scripts/bundle-macos.sh +++ b/scripts/bundle-macos.sh @@ -21,7 +21,7 @@ APP="$ROOT/dist/$APP_NAME" MACOS="$APP/Contents/MacOS" RES="$APP/Contents/Resources" FW="$APP/Contents/Frameworks" -ICON_512="$ROOT/share/icons/hicolor/512x512/apps/maturita.png" +ICON_512="$ROOT/data/share/icons/hicolor/512x512/apps/maturita.png" OUT_ZIP="maturita-macos-${ARCH_TAG}.zip" BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)" @@ -34,9 +34,9 @@ rm -rf "$APP" mkdir -p "$MACOS" "$RES/icons/hicolor/512x512/apps" "$RES/share" "$FW" cp -f "$ROOT/maturita" "$MACOS/maturita-bin" -cp -f "$ROOT/style.css" "$RES/style.css" +cp -f "$ROOT/data/style.css" "$RES/style.css" cp -f "$ICON_512" "$RES/icons/hicolor/512x512/apps/maturita.png" -cp -R "$ROOT/share/." "$RES/share/" +cp -R "$ROOT/data/share/." "$RES/share/" # Resolve an absolute Homebrew-style dependency to a real file on disk. resolve_lib() { @@ -246,7 +246,7 @@ fi # Developer ID + notarization is what lets a downloaded app open without # System Settings. Without a certificate the bundle stays ad-hoc signed, which # is enough for local runs but Gatekeeper still blocks internet downloads. -ENTITLEMENTS="$ROOT/macos/Maturita.entitlements" +ENTITLEMENTS="$ROOT/data/macos/Maturita.entitlements" pick_sign_identity() { if [[ -n "${MACOS_SIGN_IDENTITY:-}" ]]; then diff --git a/scripts/gen-icons.py b/scripts/gen-icons.py index 1cf5e4a..5974958 100755 --- a/scripts/gen-icons.py +++ b/scripts/gen-icons.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Regenerate assets/app-icon.ico and share/icons from assets/app-icon.png. +"""Regenerate assets/app-icon.ico and data/share/icons from assets/app-icon.png. Uses macOS `sips` when available; otherwise requires Pillow. Run from the repository root. @@ -71,13 +71,9 @@ def main() -> int: print(f"wrote {ICO.relative_to(ROOT)}") for size in SHARE_SIZES: - dest = ROOT / "share" / "icons" / "hicolor" / f"{size}x{size}" / "apps" / "maturita.png" + dest = ROOT / "data" / "share" / "icons" / "hicolor" / f"{size}x{size}" / "apps" / "maturita.png" resize_png(SRC, dest, size) print(f"wrote {dest.relative_to(ROOT)}") - - assets_icon = ROOT / "assets" / "icons" / "hicolor" / "512x512" / "apps" / "maturita.png" - resize_png(SRC, assets_icon, 512) - print(f"wrote {assets_icon.relative_to(ROOT)}") return 0 diff --git a/src/main.c b/src/main.c index 607d845..2a2a0f1 100644 --- a/src/main.c +++ b/src/main.c @@ -188,6 +188,7 @@ static gboolean ui_scale_tick(GtkWidget *w, GdkFrameClock *clock, static const char *find_app_icon_file(void) { static const char *paths[] = { ICON_FILE, + "../assets/app-icon.png", ICON_THEME_DIR "/hicolor/512x512/apps/" ICON_NAME ".png", SHARE_DIR "/icons/hicolor/512x512/apps/" ICON_NAME ".png", ICON_FILE_ALT, diff --git a/src/util.c b/src/util.c index 842367a..728d2fa 100644 --- a/src/util.c +++ b/src/util.c @@ -80,6 +80,22 @@ static gboolean dir_has_stylesheet(const char *dir) { return ok; } +/* Source tree keeps runtime files in data/; installed copies put them + * next to the binary (or in Contents/Resources on macOS). */ +static char *stylesheet_dir(const char *dir) { + char *child; + + if (!dir || !dir[0]) + return NULL; + if (dir_has_stylesheet(dir)) + return g_strdup(dir); + child = g_build_filename(dir, "data", NULL); + if (dir_has_stylesheet(child)) + return child; + g_free(child); + return NULL; +} + static char *macos_resources_dir(const char *exe) { char *macos_dir; char *contents; @@ -167,16 +183,22 @@ void setup_portable_paths(void) { char *write_root = NULL; char *cwd; - if (appdir && dir_has_stylesheet(appdir)) - data_dir = g_strdup(appdir); + if (appdir) + data_dir = stylesheet_dir(appdir); if (!data_dir) data_dir = macos_resources_dir(exe); - if (!data_dir && exe_dir && dir_has_stylesheet(exe_dir)) - data_dir = g_strdup(exe_dir); + if (!data_dir) + data_dir = stylesheet_dir(exe_dir); + if (!data_dir && exe_dir) { + char *parent = dup_dirname(exe_dir); + + data_dir = stylesheet_dir(parent); + g_free(parent); + } cwd = g_get_current_dir(); - if (!data_dir && dir_has_stylesheet(cwd)) - data_dir = g_strdup(cwd); + if (!data_dir) + data_dir = stylesheet_dir(cwd); if (!data_dir && cwd) data_dir = g_strdup(cwd); @@ -184,10 +206,26 @@ void setup_portable_paths(void) { write_root = dup_dirname(appimage); if (!write_root) write_root = macos_app_parent_dir(exe); + /* Keep progress/ at the project root when the binary lives in build/. */ + if (!write_root && exe_dir) { + char *parent = dup_dirname(exe_dir); + char *from_parent = stylesheet_dir(parent); + + if (from_parent) { + write_root = parent; + parent = NULL; + g_free(from_parent); + } + g_free(parent); + } if (!write_root && exe_dir) write_root = g_strdup(exe_dir); - if (!write_root && data_dir) - write_root = g_strdup(data_dir); + if (!write_root && data_dir) { + if (g_str_has_suffix(data_dir, G_DIR_SEPARATOR_S "data")) + write_root = dup_dirname(data_dir); + else + write_root = g_strdup(data_dir); + } if (!write_root) write_root = g_strdup(".");