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.

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