LCOV - code coverage report
Current view: top level - src/gui/dialogs - file_dialog_native_folder.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 1.3 % 76 1
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 1 1

            Line data    Source code
       1              : // =============================================================================
       2              : // Native file dialog implementations (Windows, macOS, Linux)
       3              : // Folder dialog implementation
       4              : // =============================================================================
       5              : 
       6              : #include <cstring>
       7              : 
       8              : #include "gui/dialogs/file_dialog.h"
       9              : 
      10              : #ifdef _WIN32
      11              : #define WIN32_LEAN_AND_MEAN
      12              : #include <shlobj.h>
      13              : #include <windows.h>
      14              : #else
      15              : // Required for popen, pclose, fgets, FILE on non-Windows builds
      16              : #include <cstdio>
      17              : #endif
      18              : 
      19              : #ifdef __APPLE__
      20              : #include <TargetConditionals.h>
      21              : #include <fcntl.h>
      22              : #include <sys/wait.h>
      23              : #include <unistd.h>
      24              : #endif
      25              : 
      26              : #ifndef _WIN32
      27              : #include <sys/wait.h>
      28              : #endif
      29              : 
      30              : namespace Amplitron {
      31              : 
      32              : #ifdef AMPLITRON_HEADLESS
      33            5 : std::string show_folder_dialog(const std::string&) { return ""; }
      34              : #else
      35              : 
      36              : #ifdef _WIN32
      37            0 : std::string show_folder_dialog(const std::string& title) {
      38            0 :     BROWSEINFOA bi;
      39            0 :     std::memset(&bi, 0, sizeof(bi));
      40            0 :     bi.lpszTitle = title.c_str();
      41            0 :     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
      42              : 
      43            0 :     LPITEMIDLIST pidl = SHBrowseForFolderA(&bi);
      44            0 :     if (!pidl) return "";
      45              : 
      46            0 :     char path[MAX_PATH];
      47            0 :     bool ok = SHGetPathFromIDListA(pidl, path);
      48            0 :     CoTaskMemFree(pidl);
      49            0 :     return ok ? std::string(path) : "";
      50              : }
      51              : 
      52              : #elif defined(__APPLE__) && !TARGET_OS_IOS
      53            0 : std::string show_folder_dialog(const std::string& title) {
      54              :     // Sanitize title for embedding in an AppleScript string literal:
      55              :     // escape backslashes first, then double-quotes.
      56            0 :     std::string safe_title;
      57            0 :     for (char c : title) {
      58            0 :         if (c == '\\') {
      59            0 :             safe_title += "\\\\";
      60            0 :         } else if (c == '"') {
      61            0 :             safe_title += "\\\"";
      62            0 :         } else {
      63            0 :             safe_title += c;
      64              :         }
      65              :     }
      66              : 
      67            0 :     std::string script = "POSIX path of (choose folder with prompt \"" + safe_title + "\")";
      68              : 
      69              :     // Use fork+exec to invoke /usr/bin/osascript directly, bypassing /bin/sh
      70              :     // so the script string is never interpreted by a shell.
      71              :     int pipefd[2];
      72            0 :     if (pipe(pipefd) != 0) return "";
      73              : 
      74            0 :     pid_t pid = fork();
      75            0 :     if (pid < 0) {
      76            0 :         close(pipefd[0]);
      77            0 :         close(pipefd[1]);
      78            0 :         return "";
      79              :     }
      80              : 
      81            0 :     if (pid == 0) {
      82              :         // Child process
      83            0 :         close(pipefd[0]);
      84            0 :         dup2(pipefd[1], STDOUT_FILENO);
      85            0 :         close(pipefd[1]);
      86            0 :         int devnull = open("/dev/null", O_WRONLY);
      87            0 :         if (devnull >= 0) {
      88            0 :             dup2(devnull, STDERR_FILENO);
      89            0 :             close(devnull);
      90            0 :         }
      91            0 :         execl("/usr/bin/osascript", "osascript", "-e", script.c_str(), nullptr);
      92            0 :         _exit(1);
      93              :     }
      94              : 
      95              :     // Parent process
      96            0 :     close(pipefd[1]);
      97              :     char buf[1024];
      98            0 :     std::string result;
      99              :     ssize_t n;
     100            0 :     while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) result.append(buf, static_cast<size_t>(n));
     101            0 :     close(pipefd[0]);
     102              : 
     103            0 :     int status = 0;
     104            0 :     waitpid(pid, &status, 0);
     105              : 
     106            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     107              :     // osascript returns paths with trailing slash; strip it for consistency
     108            0 :     if (!result.empty() && result.back() == '/') result.pop_back();
     109              : 
     110            0 :     return result;
     111            0 : }
     112              : 
     113              : #else  // Linux
     114            0 : std::string show_folder_dialog(const std::string& title) {
     115              :     // Sanitize title for single-quote shell embedding: replace ' with '\''
     116            0 :     std::string safe_title;
     117            0 :     for (char c : title) {
     118            0 :         if (c == '\'') {
     119            0 :             safe_title += "'\\''";
     120              :         } else {
     121            0 :             safe_title += c;
     122              :         }
     123              :     }
     124              : 
     125              :     std::string cmd =
     126              :         "zenity --file-selection --directory "
     127            0 :         "--title='" +
     128            0 :         safe_title + "' 2>/dev/null";
     129              : 
     130            0 :     FILE* pipe = popen(cmd.c_str(), "r");
     131            0 :     if (!pipe) return "";
     132              : 
     133              :     char buf[1024];
     134            0 :     std::string result;
     135            0 :     while (fgets(buf, sizeof(buf), pipe)) {
     136            0 :         result += buf;
     137              :     }
     138            0 :     int wait_status = pclose(pipe);
     139            0 :     int exit_code = WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : -1;
     140              : 
     141            0 :     if (exit_code != 0) {
     142              :         // Exit status 1 means the user cancelled in zenity — return empty.
     143            0 :         if (exit_code == 1) return "";
     144              : 
     145              :         // Any other non-zero status means zenity is unavailable; try kdialog.
     146            0 :         cmd = "kdialog --getexistingdirectory ~/ --title '" + safe_title + "' 2>/dev/null";
     147            0 :         pipe = popen(cmd.c_str(), "r");
     148            0 :         if (!pipe) return "";
     149            0 :         result.clear();
     150            0 :         while (fgets(buf, sizeof(buf), pipe)) {
     151            0 :             result += buf;
     152              :         }
     153            0 :         pclose(pipe);
     154              :     }
     155              : 
     156            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     157              : 
     158            0 :     return result;
     159            0 : }
     160              : #endif
     161              : 
     162              : #endif  // AMPLITRON_HEADLESS
     163              : 
     164              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1