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

            Line data    Source code
       1              : // =============================================================================
       2              : // Native file dialog implementations (Windows, macOS, Linux)
       3              : // Open dialog implementation
       4              : // =============================================================================
       5              : 
       6              : #include <cstring>
       7              : 
       8              : #include "gui/dialogs/file_dialog.h"
       9              : 
      10              : #ifdef _WIN32
      11              : // clang-format off
      12              : #define WIN32_LEAN_AND_MEAN
      13              : #include <windows.h>
      14              : #include <commdlg.h>
      15              : // clang-format on
      16              : #endif
      17              : 
      18              : #ifdef __APPLE__
      19              : #include <TargetConditionals.h>
      20              : #include <fcntl.h>
      21              : #include <sys/wait.h>
      22              : #include <unistd.h>
      23              : 
      24              : #include <cstdio>
      25              : #endif
      26              : 
      27              : #ifndef _WIN32
      28              : #include <sys/wait.h>
      29              : #endif
      30              : 
      31              : namespace Amplitron {
      32              : 
      33              : #ifdef AMPLITRON_HEADLESS
      34            3 : std::string show_open_dialog(const std::string&, const std::string&, const std::string&) {
      35            4 :     return "";
      36              : }
      37              : #else
      38              : 
      39              : #ifdef _WIN32
      40            0 : std::string show_open_dialog(const std::string& title, const std::string& filter_desc,
      41              :                              const std::string& filter_ext) {
      42            0 :     char filename[MAX_PATH] = "";
      43              : 
      44            0 :     char filter[256];
      45            0 :     std::memset(filter, 0, sizeof(filter));
      46            0 :     int pos = 0;
      47            0 :     pos += snprintf(filter + pos, 256 - pos, "%s (*.%s)", filter_desc.c_str(), filter_ext.c_str());
      48            0 :     pos++;
      49            0 :     pos += snprintf(filter + pos, 256 - pos, "*.%s", filter_ext.c_str());
      50            0 :     pos++;
      51            0 :     pos += snprintf(filter + pos, 256 - pos, "All Files (*.*)");
      52            0 :     pos++;
      53            0 :     pos += snprintf(filter + pos, 256 - pos, "*.*");
      54              : 
      55            0 :     OPENFILENAMEA ofn;
      56            0 :     std::memset(&ofn, 0, sizeof(ofn));
      57            0 :     ofn.lStructSize = sizeof(ofn);
      58            0 :     ofn.hwndOwner = NULL;
      59            0 :     ofn.lpstrFilter = filter;
      60            0 :     ofn.lpstrFile = filename;
      61            0 :     ofn.nMaxFile = MAX_PATH;
      62            0 :     ofn.lpstrTitle = title.c_str();
      63            0 :     ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
      64              : 
      65            0 :     if (GetOpenFileNameA(&ofn)) {
      66            0 :         return std::string(filename);
      67              :     }
      68            0 :     return "";
      69              : }
      70              : 
      71              : #elif defined(__APPLE__) && !TARGET_OS_IOS
      72            0 : std::string show_open_dialog(const std::string& title, const std::string& /*filter_desc*/,
      73              :                              const std::string& filter_ext) {
      74              :     // Sanitize title and filter_ext for AppleScript
      75            0 :     std::string safe_title;
      76            0 :     for (char c : title) {
      77            0 :         if (c == '\\') {
      78            0 :             safe_title += "\\\\";
      79            0 :         } else if (c == '"') {
      80            0 :             safe_title += "\\\"";
      81            0 :         } else {
      82            0 :             safe_title += c;
      83              :         }
      84              :     }
      85              : 
      86            0 :     std::string safe_ext;
      87            0 :     for (char c : filter_ext) {
      88            0 :         if (c == '\\') {
      89            0 :             safe_ext += "\\\\";
      90            0 :         } else if (c == '"') {
      91            0 :             safe_ext += "\\\"";
      92            0 :         } else {
      93            0 :             safe_ext += c;
      94              :         }
      95              :     }
      96              : 
      97            0 :     std::string script = "POSIX path of (choose file of type {\"" + safe_ext +
      98            0 :                          "\"} with prompt \"" + safe_title + "\")";
      99              : 
     100              :     // Use fork+exec to invoke osascript directly
     101              :     int pipefd[2];
     102            0 :     if (pipe(pipefd) != 0) return "";
     103              : 
     104            0 :     pid_t pid = fork();
     105            0 :     if (pid < 0) {
     106            0 :         close(pipefd[0]);
     107            0 :         close(pipefd[1]);
     108            0 :         return "";
     109              :     }
     110              : 
     111            0 :     if (pid == 0) {
     112            0 :         close(pipefd[0]);
     113            0 :         dup2(pipefd[1], STDOUT_FILENO);
     114            0 :         close(pipefd[1]);
     115            0 :         int devnull = open("/dev/null", O_WRONLY);
     116            0 :         if (devnull >= 0) {
     117            0 :             dup2(devnull, STDERR_FILENO);
     118            0 :             close(devnull);
     119            0 :         }
     120            0 :         execl("/usr/bin/osascript", "osascript", "-e", script.c_str(), nullptr);
     121            0 :         _exit(1);
     122              :     }
     123              : 
     124            0 :     close(pipefd[1]);
     125              :     char buf[1024];
     126            0 :     std::string result;
     127              :     ssize_t n;
     128            0 :     while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) result.append(buf, static_cast<size_t>(n));
     129            0 :     close(pipefd[0]);
     130              : 
     131            0 :     int status = 0;
     132            0 :     waitpid(pid, &status, 0);
     133              : 
     134            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     135              : 
     136            0 :     return result;
     137            0 : }
     138              : 
     139              : #else  // Linux
     140            0 : std::string show_open_dialog(const std::string& title, const std::string& filter_desc,
     141              :                              const std::string& filter_ext) {
     142              :     // Escape single quotes for shell
     143            0 :     auto escape_single_quotes = [](const std::string& s) {
     144            0 :         std::string result;
     145            0 :         for (char c : s) {
     146            0 :             if (c == '\'') {
     147            0 :                 result += "'\\''";
     148              :             } else {
     149            0 :                 result += c;
     150              :             }
     151              :         }
     152            0 :         return result;
     153            0 :     };
     154              : 
     155            0 :     std::string safe_title = escape_single_quotes(title);
     156            0 :     std::string safe_desc = escape_single_quotes(filter_desc);
     157            0 :     std::string safe_ext = escape_single_quotes(filter_ext);
     158              : 
     159              :     std::string cmd =
     160              :         "zenity --file-selection "
     161            0 :         "--title='" +
     162            0 :         safe_title +
     163              :         "' "
     164            0 :         "--file-filter='" +
     165            0 :         safe_desc + " (*." + safe_ext + ")|*." + safe_ext +
     166              :         "' "
     167            0 :         "--file-filter='All Files (*)|*' 2>/dev/null";
     168              : 
     169            0 :     FILE* pipe = popen(cmd.c_str(), "r");
     170            0 :     if (!pipe) return "";
     171              : 
     172              :     char buf[1024];
     173            0 :     std::string result;
     174            0 :     while (fgets(buf, sizeof(buf), pipe)) {
     175            0 :         result += buf;
     176              :     }
     177            0 :     int wait_status = pclose(pipe);
     178              : 
     179            0 :     if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status) != 0) {
     180            0 :         cmd = "kdialog --getopenfilename ~/ '*." + safe_ext + "|" + safe_desc +
     181              :               "' "
     182            0 :               "--title '" +
     183            0 :               safe_title + "' 2>/dev/null";
     184            0 :         pipe = popen(cmd.c_str(), "r");
     185            0 :         if (!pipe) return "";
     186            0 :         result.clear();
     187            0 :         while (fgets(buf, sizeof(buf), pipe)) {
     188            0 :             result += buf;
     189              :         }
     190            0 :         pclose(pipe);
     191              :     }
     192              : 
     193            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     194              : 
     195            0 :     return result;
     196            0 : }
     197              : #endif
     198              : 
     199              : #endif  // AMPLITRON_HEADLESS
     200              : 
     201              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1