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

Generated by: LCOV version 2.0-1