You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

786 lines
20KB

  1. #include <thread>
  2. #include <regex>
  3. #include <chrono>
  4. #include <ghc/filesystem.hpp>
  5. #include <dirent.h>
  6. #include <sys/stat.h>
  7. #include <cxxabi.h> // for __cxxabiv1::__cxa_demangle
  8. #if defined ARCH_LIN || defined ARCH_MAC
  9. #include <pthread.h>
  10. #include <time.h> // for clock_gettime
  11. #include <sched.h>
  12. #include <execinfo.h> // for backtrace and backtrace_symbols
  13. #include <unistd.h> // for execl
  14. #include <sys/utsname.h>
  15. #endif
  16. #if defined ARCH_MAC
  17. #include <mach/mach_init.h>
  18. #include <mach/thread_act.h>
  19. #include <mach/clock.h>
  20. #include <mach/mach.h>
  21. #endif
  22. #if defined ARCH_WIN
  23. #include <windows.h>
  24. #include <shellapi.h>
  25. #include <processthreadsapi.h>
  26. #include <dbghelp.h>
  27. #endif
  28. #include <archive.h>
  29. #include <archive_entry.h>
  30. #include <system.hpp>
  31. #include <string.hpp>
  32. /*
  33. In C++17, this will be `std::filesystem`
  34. Important: When using `fs::path`, always convert strings to UTF-8 using
  35. fs::path p = fs::u8path(s);
  36. In fact, it's best to work only with strings to avoid forgetting to decode a string as UTF-8.
  37. The need to do this is a fatal flaw of `fs::path`, but at least `std::filesystem` has some helpful operations.
  38. */
  39. namespace fs = ghc::filesystem;
  40. namespace rack {
  41. namespace system {
  42. std::string join(const std::string& path1, const std::string& path2) {
  43. return (fs::u8path(path1) / fs::u8path(path2)).generic_u8string();
  44. }
  45. static void appendEntries(std::vector<std::string>& entries, const fs::path& dir, int depth) {
  46. try {
  47. for (const auto& entry : fs::directory_iterator(dir)) {
  48. entries.push_back(entry.path().generic_u8string());
  49. // Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion).
  50. if (depth != 0) {
  51. if (entry.is_directory()) {
  52. appendEntries(entries, entry.path(), depth - 1);
  53. }
  54. }
  55. }
  56. }
  57. catch (fs::filesystem_error& e) {}
  58. }
  59. std::vector<std::string> getEntries(const std::string& dirPath, int depth) {
  60. std::vector<std::string> entries;
  61. appendEntries(entries, fs::u8path(dirPath), depth);
  62. return entries;
  63. }
  64. bool exists(const std::string& path) {
  65. try {
  66. return fs::exists(fs::u8path(path));
  67. }
  68. catch (fs::filesystem_error& e) {
  69. return false;
  70. }
  71. }
  72. bool isFile(const std::string& path) {
  73. try {
  74. return fs::is_regular_file(fs::u8path(path));
  75. }
  76. catch (fs::filesystem_error& e) {
  77. return false;
  78. }
  79. }
  80. bool isDirectory(const std::string& path) {
  81. try {
  82. return fs::is_directory(fs::u8path(path));
  83. }
  84. catch (fs::filesystem_error& e) {
  85. return false;
  86. }
  87. }
  88. uint64_t getFileSize(const std::string& path) {
  89. try {
  90. return fs::file_size(fs::u8path(path));
  91. }
  92. catch (fs::filesystem_error& e) {
  93. return 0;
  94. }
  95. }
  96. bool rename(const std::string& srcPath, const std::string& destPath) {
  97. try {
  98. fs::rename(fs::u8path(srcPath), fs::u8path(destPath));
  99. return true;
  100. }
  101. catch (fs::filesystem_error& e) {
  102. return false;
  103. }
  104. }
  105. bool copy(const std::string& srcPath, const std::string& destPath) {
  106. try {
  107. fs::copy(fs::u8path(srcPath), fs::u8path(destPath), fs::copy_options::recursive);
  108. return true;
  109. }
  110. catch (fs::filesystem_error& e) {
  111. return false;
  112. }
  113. }
  114. bool createDirectory(const std::string& path) {
  115. try {
  116. return fs::create_directory(fs::u8path(path));
  117. }
  118. catch (fs::filesystem_error& e) {
  119. return false;
  120. }
  121. }
  122. bool createDirectories(const std::string& path) {
  123. try {
  124. return fs::create_directories(fs::u8path(path));
  125. }
  126. catch (fs::filesystem_error& e) {
  127. return false;
  128. }
  129. }
  130. bool remove(const std::string& path) {
  131. try {
  132. return fs::remove(fs::u8path(path));
  133. }
  134. catch (fs::filesystem_error& e) {
  135. return false;
  136. }
  137. }
  138. int removeRecursively(const std::string& path) {
  139. try {
  140. return fs::remove_all(fs::u8path(path));
  141. }
  142. catch (fs::filesystem_error& e) {
  143. return 0;
  144. }
  145. }
  146. std::string getWorkingDirectory() {
  147. try {
  148. return fs::current_path().generic_u8string();
  149. }
  150. catch (fs::filesystem_error& e) {
  151. return "";
  152. }
  153. }
  154. void setWorkingDirectory(const std::string& path) {
  155. try {
  156. fs::current_path(fs::u8path(path));
  157. }
  158. catch (fs::filesystem_error& e) {
  159. // Do nothing
  160. }
  161. }
  162. std::string getTempDirectory() {
  163. try {
  164. return fs::temp_directory_path().generic_u8string();
  165. }
  166. catch (fs::filesystem_error& e) {
  167. return "";
  168. }
  169. }
  170. std::string getAbsolute(const std::string& path) {
  171. try {
  172. return fs::absolute(fs::u8path(path)).generic_u8string();
  173. }
  174. catch (fs::filesystem_error& e) {
  175. return "";
  176. }
  177. }
  178. std::string getCanonical(const std::string& path) {
  179. try {
  180. return fs::canonical(fs::u8path(path)).generic_u8string();
  181. }
  182. catch (fs::filesystem_error& e) {
  183. return "";
  184. }
  185. }
  186. std::string getDirectory(const std::string& path) {
  187. try {
  188. return fs::u8path(path).parent_path().generic_u8string();
  189. }
  190. catch (fs::filesystem_error& e) {
  191. return "";
  192. }
  193. }
  194. std::string getFilename(const std::string& path) {
  195. try {
  196. return fs::u8path(path).filename().generic_u8string();
  197. }
  198. catch (fs::filesystem_error& e) {
  199. return "";
  200. }
  201. }
  202. std::string getStem(const std::string& path) {
  203. try {
  204. return fs::u8path(path).stem().generic_u8string();
  205. }
  206. catch (fs::filesystem_error& e) {
  207. return "";
  208. }
  209. }
  210. std::string getExtension(const std::string& path) {
  211. try {
  212. return fs::u8path(path).extension().generic_u8string();
  213. }
  214. catch (fs::filesystem_error& e) {
  215. return "";
  216. }
  217. }
  218. std::vector<uint8_t> readFile(const std::string& path) {
  219. std::vector<uint8_t> data;
  220. FILE* f = std::fopen(path.c_str(), "rb");
  221. if (!f)
  222. throw Exception("Cannot read file %s", path.c_str());
  223. DEFER({
  224. std::fclose(f);
  225. });
  226. // Get file size so we can make a single allocation
  227. std::fseek(f, 0, SEEK_END);
  228. size_t len = std::ftell(f);
  229. std::fseek(f, 0, SEEK_SET);
  230. data.resize(len);
  231. std::fread(data.data(), 1, len, f);
  232. return data;
  233. }
  234. void writeFile(const std::string& path, const std::vector<uint8_t>& data) {
  235. FILE* f = std::fopen(path.c_str(), "wb");
  236. if (!f)
  237. throw Exception("Cannot create file %s", path.c_str());
  238. DEFER({
  239. std::fclose(f);
  240. });
  241. std::fwrite(data.data(), 1, data.size(), f);
  242. }
  243. /** Returns `p` in relative path form, relative to `base`
  244. Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path.
  245. */
  246. static std::string getRelativePath(std::string path, std::string base) {
  247. try {
  248. path = fs::absolute(fs::u8path(path)).generic_u8string();
  249. base = fs::absolute(fs::u8path(base)).generic_u8string();
  250. }
  251. catch (fs::filesystem_error& e) {
  252. throw Exception("%s", e.what());
  253. }
  254. if (path.size() < base.size())
  255. throw Exception("getRelativePath() error: path is shorter than base");
  256. if (!std::equal(base.begin(), base.end(), path.begin()))
  257. throw Exception("getRelativePath() error: path does not begin with base");
  258. // If path == base, this correctly returns "."
  259. return "." + std::string(path.begin() + base.size(), path.end());
  260. }
  261. static la_ssize_t archiveWriteVectorCallback(struct archive* a, void* client_data, const void* buffer, size_t length) {
  262. assert(client_data);
  263. std::vector<uint8_t>& data = *((std::vector<uint8_t>*) client_data);
  264. uint8_t* buf = (uint8_t*) buffer;
  265. data.insert(data.end(), buf, buf + length);
  266. return length;
  267. }
  268. static void archiveDirectory(const std::string& archivePath, std::vector<uint8_t>* archiveData, const std::string& dirPath, int compressionLevel) {
  269. // Based on minitar.c create() in libarchive examples
  270. int r;
  271. // Open archive for writing
  272. struct archive* a = archive_write_new();
  273. DEFER({archive_write_free(a);});
  274. // For some reason libarchive adds 10k of padding to archive_write_open() (but not archive_write_open_filename()) unless this is set to 0.
  275. archive_write_set_bytes_per_block(a, 0);
  276. archive_write_set_format_ustar(a);
  277. archive_write_add_filter_zstd(a);
  278. assert(0 <= compressionLevel && compressionLevel <= 19);
  279. r = archive_write_set_filter_option(a, NULL, "compression-level", std::to_string(compressionLevel).c_str());
  280. if (r < ARCHIVE_OK)
  281. throw Exception("Archiver could not set filter option: %s", archive_error_string(a));
  282. if (archiveData) {
  283. // Open vector
  284. archive_write_open(a, (void*) archiveData, NULL, archiveWriteVectorCallback, NULL);
  285. }
  286. else {
  287. // Open file
  288. #if defined ARCH_WIN
  289. r = archive_write_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str());
  290. #else
  291. r = archive_write_open_filename(a, archivePath.c_str());
  292. #endif
  293. if (r < ARCHIVE_OK)
  294. throw Exception("Archiver could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a));
  295. }
  296. DEFER({archive_write_close(a);});
  297. // Open dir for reading
  298. struct archive* disk = archive_read_disk_new();
  299. DEFER({archive_read_free(disk);});
  300. #if defined ARCH_WIN
  301. r = archive_read_disk_open_w(disk, string::UTF8toUTF16(dirPath).c_str());
  302. #else
  303. r = archive_read_disk_open(disk, dirPath.c_str());
  304. #endif
  305. if (r < ARCHIVE_OK)
  306. throw Exception("Archiver could not open dir %s for reading: %s", dirPath.c_str(), archive_error_string(a));
  307. DEFER({archive_read_close(a);});
  308. // Iterate dir
  309. for (;;) {
  310. struct archive_entry* entry = archive_entry_new();
  311. DEFER({archive_entry_free(entry);});
  312. r = archive_read_next_header2(disk, entry);
  313. if (r == ARCHIVE_EOF)
  314. break;
  315. if (r < ARCHIVE_OK)
  316. throw Exception("Archiver could not get next entry from archive: %s", archive_error_string(disk));
  317. // Recurse dirs
  318. archive_read_disk_descend(disk);
  319. // Convert absolute path to relative path
  320. std::string entryPath;
  321. #if defined ARCH_WIN
  322. entryPath = string::UTF16toUTF8(archive_entry_pathname_w(entry));
  323. #else
  324. entryPath = archive_entry_pathname(entry);
  325. #endif
  326. entryPath = getRelativePath(entryPath, dirPath);
  327. #if defined ARCH_WIN
  328. // FIXME This doesn't seem to set UTF-8 paths on Windows.
  329. archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(entryPath).c_str());
  330. #else
  331. archive_entry_set_pathname(entry, entryPath.c_str());
  332. #endif
  333. // Write file to archive
  334. r = archive_write_header(a, entry);
  335. if (r < ARCHIVE_OK)
  336. throw Exception("Archiver could not write entry to archive: %s", archive_error_string(a));
  337. // Manually copy data
  338. #if defined ARCH_WIN
  339. std::string entrySourcePath = string::UTF16toUTF8(archive_entry_sourcepath_w(entry));
  340. #else
  341. std::string entrySourcePath = archive_entry_sourcepath(entry);
  342. #endif
  343. FILE* f = std::fopen(entrySourcePath.c_str(), "rb");
  344. DEFER({std::fclose(f);});
  345. char buf[1 << 16];
  346. ssize_t len;
  347. while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) {
  348. archive_write_data(a, buf, len);
  349. }
  350. }
  351. }
  352. void archiveDirectory(const std::string& archivePath, const std::string& dirPath, int compressionLevel) {
  353. archiveDirectory(archivePath, NULL, dirPath, compressionLevel);
  354. }
  355. std::vector<uint8_t> archiveDirectory(const std::string& dirPath, int compressionLevel) {
  356. std::vector<uint8_t> archiveData;
  357. archiveDirectory("", &archiveData, dirPath, compressionLevel);
  358. return archiveData;
  359. }
  360. struct ArchiveReadVectorData {
  361. const std::vector<uint8_t>* data = NULL;
  362. size_t pos = 0;
  363. };
  364. static la_ssize_t archiveReadVectorCallback(struct archive *a, void* client_data, const void** buffer) {
  365. assert(client_data);
  366. ArchiveReadVectorData* arvd = (ArchiveReadVectorData*) client_data;
  367. assert(arvd->data);
  368. const std::vector<uint8_t>& data = *arvd->data;
  369. *buffer = &data[arvd->pos];
  370. // Read up to some block size of bytes
  371. size_t len = std::min(data.size() - arvd->pos, size_t(1 << 16));
  372. arvd->pos += len;
  373. return len;
  374. }
  375. static void unarchiveToDirectory(const std::string& archivePath, const std::vector<uint8_t>* archiveData, const std::string& dirPath) {
  376. // Based on minitar.c extract() in libarchive examples
  377. int r;
  378. // Open archive for reading
  379. struct archive* a = archive_read_new();
  380. DEFER({archive_read_free(a);});
  381. archive_read_support_filter_zstd(a);
  382. // archive_read_support_filter_all(a);
  383. archive_read_support_format_tar(a);
  384. // archive_read_support_format_all(a);
  385. ArchiveReadVectorData arvd;
  386. if (archiveData) {
  387. // Open vector
  388. arvd.data = archiveData;
  389. archive_read_open(a, &arvd, NULL, archiveReadVectorCallback, NULL);
  390. }
  391. else {
  392. // Open file
  393. #if defined ARCH_WIN
  394. r = archive_read_open_filename_w(a, string::UTF8toUTF16(archivePath).c_str(), 1 << 16);
  395. #else
  396. r = archive_read_open_filename(a, archivePath.c_str(), 1 << 16);
  397. #endif
  398. if (r < ARCHIVE_OK)
  399. throw Exception("Unarchiver could not open archive %s: %s", archivePath.c_str(), archive_error_string(a));
  400. }
  401. DEFER({archive_read_close(a);});
  402. // Open dir for writing
  403. struct archive* disk = archive_write_disk_new();
  404. DEFER({archive_write_free(disk);});
  405. int flags = ARCHIVE_EXTRACT_TIME;
  406. archive_write_disk_set_options(disk, flags);
  407. DEFER({archive_write_close(disk);});
  408. // Iterate archive
  409. for (;;) {
  410. // Get next entry
  411. struct archive_entry* entry;
  412. r = archive_read_next_header(a, &entry);
  413. if (r == ARCHIVE_EOF)
  414. break;
  415. if (r < ARCHIVE_OK)
  416. throw Exception("Unarchiver could not read entry from archive: %s", archive_error_string(a));
  417. // Convert relative pathname to absolute based on dirPath
  418. std::string entryPath = archive_entry_pathname(entry);
  419. if (!fs::u8path(entryPath).is_relative())
  420. throw Exception("Unarchiver does not support absolute tar paths: %s", entryPath.c_str());
  421. entryPath = (fs::u8path(dirPath) / fs::u8path(entryPath)).generic_u8string();
  422. #if defined ARCH_WIN
  423. archive_entry_copy_pathname_w(entry, string::UTF8toUTF16(entryPath).c_str());
  424. #else
  425. archive_entry_set_pathname(entry, entryPath.c_str());
  426. #endif
  427. // Write entry to disk
  428. r = archive_write_header(disk, entry);
  429. if (r < ARCHIVE_OK)
  430. throw Exception("Unarchiver could not write file to dir: %s", archive_error_string(disk));
  431. // Copy data to file
  432. for (;;) {
  433. const void* buf;
  434. size_t size;
  435. int64_t offset;
  436. // Read data from archive
  437. r = archive_read_data_block(a, &buf, &size, &offset);
  438. if (r == ARCHIVE_EOF)
  439. break;
  440. if (r < ARCHIVE_OK)
  441. throw Exception("Unarchiver could not read data from archive: %s", archive_error_string(a));
  442. // Write data to file
  443. r = archive_write_data_block(disk, buf, size, offset);
  444. if (r < ARCHIVE_OK)
  445. throw Exception("Unarchiver could not write data to file: %s", archive_error_string(disk));
  446. }
  447. // Close file
  448. r = archive_write_finish_entry(disk);
  449. if (r < ARCHIVE_OK)
  450. throw Exception("Unarchiver could not close file: %s", archive_error_string(disk));
  451. }
  452. }
  453. void unarchiveToDirectory(const std::string& archivePath, const std::string& dirPath) {
  454. unarchiveToDirectory(archivePath, NULL, dirPath);
  455. }
  456. void unarchiveToDirectory(const std::vector<uint8_t>& archiveData, const std::string& dirPath) {
  457. unarchiveToDirectory("", &archiveData, dirPath);
  458. }
  459. int getLogicalCoreCount() {
  460. return std::thread::hardware_concurrency();
  461. }
  462. void setThreadName(const std::string& name) {
  463. #if defined ARCH_LIN
  464. pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
  465. #elif defined ARCH_MAC
  466. // Not supported (yet) on Mac
  467. #elif defined ARCH_WIN
  468. // Not supported on Windows
  469. #endif
  470. }
  471. std::string getStackTrace() {
  472. int stackLen = 128;
  473. void* stack[stackLen];
  474. std::string s;
  475. #if defined ARCH_LIN || defined ARCH_MAC
  476. stackLen = backtrace(stack, stackLen);
  477. char** strings = backtrace_symbols(stack, stackLen);
  478. // Skip the first line because it's this function.
  479. for (int i = 1; i < stackLen; i++) {
  480. s += string::f("%d: ", stackLen - i - 1);
  481. std::string line = strings[i];
  482. #if 0
  483. // Parse line
  484. std::regex r(R"((.*)\((.*)\+(.*)\) (.*))");
  485. std::smatch match;
  486. if (std::regex_search(line, match, r)) {
  487. s += match[1].str();
  488. s += "(";
  489. std::string symbol = match[2].str();
  490. // Demangle symbol
  491. char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL);
  492. if (symbolD) {
  493. symbol = symbolD;
  494. free(symbolD);
  495. }
  496. s += symbol;
  497. s += "+";
  498. s += match[3].str();
  499. s += ")";
  500. }
  501. #else
  502. s += line;
  503. #endif
  504. s += "\n";
  505. }
  506. free(strings);
  507. #elif defined ARCH_WIN
  508. HANDLE process = GetCurrentProcess();
  509. SymInitialize(process, NULL, true);
  510. stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL);
  511. SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1);
  512. symbol->MaxNameLen = 255;
  513. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  514. for (int i = 1; i < stackLen; i++) {
  515. SymFromAddr(process, (DWORD64) stack[i], 0, symbol);
  516. s += string::f("%d: %s 0x%" PRIx64 "\n", stackLen - i - 1, symbol->Name, symbol->Address);
  517. }
  518. free(symbol);
  519. #endif
  520. return s;
  521. }
  522. #if defined ARCH_WIN
  523. static int64_t startCounter = 0;
  524. static double counterTime = 0.0;
  525. #endif
  526. #if defined ARCH_LIN || defined ARCH_MAC
  527. static int64_t startTime = 0;
  528. #endif
  529. static void initTime() {
  530. #if defined ARCH_WIN
  531. LARGE_INTEGER counter;
  532. QueryPerformanceCounter(&counter);
  533. startCounter = counter.QuadPart;
  534. LARGE_INTEGER frequency;
  535. QueryPerformanceFrequency(&frequency);
  536. counterTime = 1.0 / frequency.QuadPart;
  537. #endif
  538. #if defined ARCH_LIN
  539. struct timespec ts;
  540. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  541. startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  542. #endif
  543. #if defined ARCH_MAC
  544. clock_serv_t cclock;
  545. mach_timespec_t mts;
  546. host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
  547. clock_get_time(cclock, &mts);
  548. mach_port_deallocate(mach_task_self(), cclock);
  549. startTime = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec;
  550. #endif
  551. }
  552. double getTime() {
  553. #if defined ARCH_WIN
  554. LARGE_INTEGER counter;
  555. QueryPerformanceCounter(&counter);
  556. return (counter.QuadPart - startCounter) * counterTime;
  557. #endif
  558. #if defined ARCH_LIN
  559. struct timespec ts;
  560. clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  561. int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
  562. return (time - startTime) / 1e9;
  563. #endif
  564. #if defined ARCH_MAC
  565. clock_serv_t cclock;
  566. mach_timespec_t mts;
  567. host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
  568. clock_get_time(cclock, &mts);
  569. mach_port_deallocate(mach_task_self(), cclock);
  570. int64_t time = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec;
  571. return (time - startTime) / 1e9;
  572. #endif
  573. }
  574. double getUnixTime() {
  575. // This is not guaranteed to return the time since 1970 in C++11. (It only does in C++20).
  576. // However, it does on all platforms I care about.
  577. auto duration = std::chrono::system_clock::now().time_since_epoch();
  578. return std::chrono::duration<double>(duration).count();
  579. }
  580. std::string getOperatingSystemInfo() {
  581. #if defined ARCH_LIN || defined ARCH_MAC
  582. struct utsname u;
  583. uname(&u);
  584. return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine);
  585. #elif defined ARCH_WIN
  586. OSVERSIONINFOW info;
  587. ZeroMemory(&info, sizeof(info));
  588. info.dwOSVersionInfoSize = sizeof(info);
  589. GetVersionExW(&info);
  590. // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers.
  591. return string::f("Windows %lu.%lu", info.dwMajorVersion, info.dwMinorVersion);
  592. #endif
  593. }
  594. void openBrowser(const std::string& url) {
  595. if (url.empty())
  596. return;
  597. std::string urlL = url;
  598. std::thread t([=] {
  599. #if defined ARCH_LIN
  600. std::string command = "xdg-open \"" + urlL + "\"";
  601. (void) std::system(command.c_str());
  602. #endif
  603. #if defined ARCH_MAC
  604. std::string command = "open \"" + urlL + "\"";
  605. std::system(command.c_str());
  606. #endif
  607. #if defined ARCH_WIN
  608. ShellExecuteW(NULL, L"open", string::UTF8toUTF16(urlL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  609. #endif
  610. });
  611. t.detach();
  612. }
  613. void openDirectory(const std::string& path) {
  614. if (path.empty())
  615. return;
  616. std::string pathL = path;
  617. std::thread t([=] {
  618. #if defined ARCH_LIN
  619. std::string command = "xdg-open \"" + pathL + "\"";
  620. (void) std::system(command.c_str());
  621. #endif
  622. #if defined ARCH_MAC
  623. std::string command = "open \"" + pathL + "\"";
  624. std::system(command.c_str());
  625. #endif
  626. #if defined ARCH_WIN
  627. ShellExecuteW(NULL, L"explore", string::UTF8toUTF16(pathL).c_str(), NULL, NULL, SW_SHOWDEFAULT);
  628. #endif
  629. });
  630. t.detach();
  631. }
  632. void runProcessDetached(const std::string& path) {
  633. #if defined ARCH_WIN
  634. SHELLEXECUTEINFOW shExInfo;
  635. ZeroMemory(&shExInfo, sizeof(shExInfo));
  636. shExInfo.cbSize = sizeof(shExInfo);
  637. shExInfo.lpVerb = L"runas";
  638. std::wstring pathW = string::UTF8toUTF16(path);
  639. shExInfo.lpFile = pathW.c_str();
  640. shExInfo.nShow = SW_SHOW;
  641. if (ShellExecuteExW(&shExInfo)) {
  642. // Do nothing
  643. }
  644. #else
  645. // Not implemented on Linux or Mac
  646. assert(0);
  647. #endif
  648. }
  649. void init() {
  650. initTime();
  651. }
  652. } // namespace system
  653. } // namespace rack