LCOV - code coverage report
Current view: top level - src/gui/dialogs - file_dialog_native.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 2.6 % 78 2
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              : // Save 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              : #include <shlobj.h>
      16              : // clang-format on
      17              : #endif
      18              : 
      19              : #ifdef __APPLE__
      20              : #include <TargetConditionals.h>
      21              : #include <fcntl.h>
      22              : #include <sys/wait.h>
      23              : #include <unistd.h>
      24              : 
      25              : #include <cstdio>
      26              : #endif
      27              : 
      28              : #ifndef _WIN32
      29              : #include <sys/wait.h>
      30              : #endif
      31              : 
      32              : namespace Amplitron {
      33              : 
      34              : #ifdef AMPLITRON_HEADLESS
      35            6 : std::string show_save_dialog(const std::string&, const std::string&, const std::string&) {
      36            8 :     return "";
      37              : }
      38              : #else
      39              : 
      40              : #ifdef _WIN32
      41            0 : std::string show_save_dialog(const std::string& default_name, const std::string& filter_desc,
      42              :                              const std::string& filter_ext) {
      43            0 :     char filename[MAX_PATH];
      44            0 :     std::strncpy(filename, default_name.c_str(), MAX_PATH - 1);
      45            0 :     filename[MAX_PATH - 1] = '\0';
      46              : 
      47              :     // Build filter string: "WAV Audio (*.wav)\0*.wav\0All Files (*.*)\0*.*\0\0"
      48            0 :     char filter[256];
      49            0 :     std::memset(filter, 0, sizeof(filter));
      50            0 :     int pos = 0;
      51            0 :     pos += snprintf(filter + pos, 256 - pos, "%s (*.%s)", filter_desc.c_str(), filter_ext.c_str());
      52            0 :     pos++;  // null separator
      53            0 :     pos += snprintf(filter + pos, 256 - pos, "*.%s", filter_ext.c_str());
      54            0 :     pos++;  // null separator
      55            0 :     pos += snprintf(filter + pos, 256 - pos, "All Files (*.*)");
      56            0 :     pos++;
      57            0 :     pos += snprintf(filter + pos, 256 - pos, "*.*");
      58              :     // double null terminator is already there from memset
      59              : 
      60              :     // Get desktop/documents as initial dir
      61            0 :     char initial_dir[MAX_PATH] = "";
      62            0 :     SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, 0, initial_dir);
      63              : 
      64            0 :     OPENFILENAMEA ofn;
      65            0 :     std::memset(&ofn, 0, sizeof(ofn));
      66            0 :     ofn.lStructSize = sizeof(ofn);
      67            0 :     ofn.hwndOwner = NULL;
      68            0 :     ofn.lpstrFilter = filter;
      69            0 :     ofn.lpstrFile = filename;
      70            0 :     ofn.nMaxFile = MAX_PATH;
      71            0 :     ofn.lpstrInitialDir = initial_dir;
      72            0 :     ofn.lpstrTitle = "Save Recording As";
      73            0 :     ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
      74            0 :     ofn.lpstrDefExt = filter_ext.c_str();
      75              : 
      76            0 :     if (GetSaveFileNameA(&ofn)) {
      77            0 :         return std::string(filename);
      78              :     }
      79            0 :     return "";
      80              : }
      81              : 
      82              : #elif defined(__APPLE__) && !TARGET_OS_IOS
      83            0 : std::string show_save_dialog(const std::string& default_name, const std::string& /*filter_desc*/,
      84              :                              const std::string& filter_ext) {
      85              :     // Use osascript to show a native NSSavePanel
      86              :     std::string cmd =
      87            0 :         "osascript -e 'set theFile to POSIX path of (choose file name "
      88              :         "with prompt \"Save Recording As\" "
      89            0 :         "default name \"" +
      90            0 :         default_name + "\")' 2>/dev/null";
      91              : 
      92            0 :     FILE* pipe = popen(cmd.c_str(), "r");
      93            0 :     if (!pipe) return "";
      94              : 
      95              :     char buf[1024];
      96            0 :     std::string result;
      97            0 :     while (fgets(buf, sizeof(buf), pipe)) {
      98            0 :         result += buf;
      99              :     }
     100            0 :     pclose(pipe);
     101              : 
     102              :     // Trim trailing newline
     103            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     104              : 
     105            0 :     if (result.empty()) return "";
     106              : 
     107              :     // Ensure it ends with the correct extension
     108            0 :     if (result.size() < filter_ext.size() + 1 ||
     109            0 :         result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
     110            0 :         result += "." + filter_ext;
     111            0 :     }
     112              : 
     113            0 :     return result;
     114            0 : }
     115              : 
     116              : #else  // Linux
     117            0 : std::string show_save_dialog(const std::string& default_name, const std::string& filter_desc,
     118              :                              const std::string& filter_ext) {
     119              :     // Try zenity first, then kdialog
     120              :     std::string cmd =
     121              :         "zenity --file-selection --save --confirm-overwrite "
     122              :         "--title='Save Recording As' "
     123            0 :         "--filename='" +
     124            0 :         default_name +
     125              :         "' "
     126            0 :         "--file-filter='" +
     127            0 :         filter_desc + " (*." + filter_ext + ")|*." + filter_ext +
     128              :         "' "
     129            0 :         "--file-filter='All Files (*)|*' 2>/dev/null";
     130              : 
     131            0 :     FILE* pipe = popen(cmd.c_str(), "r");
     132            0 :     if (!pipe) return "";
     133              : 
     134              :     char buf[1024];
     135            0 :     std::string result;
     136            0 :     while (fgets(buf, sizeof(buf), pipe)) {
     137            0 :         result += buf;
     138              :     }
     139            0 :     int status = pclose(pipe);
     140              : 
     141              :     // If zenity failed (not installed), try kdialog
     142            0 :     if (status != 0) {
     143            0 :         cmd = "kdialog --getsavefilename ~/ '*." + filter_ext + "|" + filter_desc +
     144              :               "' "
     145            0 :               "--title 'Save Recording As' 2>/dev/null";
     146            0 :         pipe = popen(cmd.c_str(), "r");
     147            0 :         if (!pipe) return "";
     148            0 :         result.clear();
     149            0 :         while (fgets(buf, sizeof(buf), pipe)) {
     150            0 :             result += buf;
     151              :         }
     152            0 :         pclose(pipe);
     153              :     }
     154              : 
     155              :     // Trim trailing newline
     156            0 :     while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
     157              : 
     158            0 :     if (result.empty()) return "";
     159              : 
     160              :     // Ensure it ends with the correct extension
     161            0 :     if (result.size() < filter_ext.size() + 1 ||
     162            0 :         result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
     163            0 :         result += "." + filter_ext;
     164              :     }
     165              : 
     166            0 :     return result;
     167            0 : }
     168              : #endif
     169              : 
     170              : #endif  // AMPLITRON_HEADLESS
     171              : 
     172              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1