// SPDX-License-Identifier: GPL-2.0 /* windows.c */ /* implements Windows specific functions */ #include "ssrf.h" #include #include "dive.h" #include "device.h" #include "libdivecomputer.h" #include "file.h" #include "errorhelper.h" #include "subsurfacesysinfo.h" #undef _WIN32_WINNT #define _WIN32_WINNT 0x500 #include #include #include #include #include #include #include #include #include /* this function converts a win32's utf-16 2 byte string to utf-8. * note: the standard library's was deprecated and is in * an ominous state, so use the native Windows version for now. */ static std::string utf16_to_utf8_fl(const std::wstring &utf16, const char *file, int line) { assert(file != NULL); assert(line); /* estimate buffer size */ const int sz = WideCharToMultiByte(CP_UTF8, 0, utf16.c_str(), -1, NULL, 0, NULL, NULL); if (!sz) { report_info("%s:%d: cannot estimate buffer size", file, line); return std::string(); } std::string utf8(sz, ' '); // Note: includes the terminating '\0', just in case. if (WideCharToMultiByte(CP_UTF8, 0, utf16.c_str(), -1, &utf8[0], sz, NULL, NULL)) { utf8.resize(sz - 1); // Chop off final '\0' byte return utf8; } report_info("%s:%d: cannot convert string", file, line); return std::string(); } /* this function converts a utf-8 string to win32's utf-16 2 byte string. */ static std::wstring utf8_to_utf16_fl(const char *utf8, const char *file, int line) { assert(file != NULL); assert(line); /* estimate buffer size */ const int sz = strlen(utf8) + 1; std::wstring utf16(sz, ' '); // Note: includes the terminating '\0', just in case. int actual_size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, &utf16[0], sz); if (actual_size) { utf16.resize(actual_size - 1); // Chop off final '\0' character return utf16; } report_info("%s:%d: cannot convert string", file, line); return std::wstring(); } #define utf16_to_utf8(s) utf16_to_utf8_fl(s, __FILE__, __LINE__) /* this function returns the Win32 Roaming path for the current user as UTF-8. * it never returns an empty string but fallsback to .\ instead! */ static std::wstring system_default_path() { wchar_t wpath[MAX_PATH] = { 0 }; const char *fname = "system_default_path()"; /* obtain the user path via SHGetFolderPathW. * this API is deprecated but still supported on modern Win32. * fallback to .\ if it fails. */ std::wstring path; if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, wpath))) { path = wpath; } else { report_info("%s: cannot obtain path!", fname); path = L'.'; } return path + L"\\Subsurface"; } /* obtain the Roaming path and append "\\.xml" to it. */ static std::wstring make_default_filename() { wchar_t username[UNLEN + 1] = { 0 }; DWORD username_len = UNLEN + 1; GetUserNameW(username, &username_len); std::wstring filename = username; filename += L".xml"; std::wstring path = system_default_path(); return path + L"\\" + filename; } extern "C" { const char non_standard_system_divelist_default_font[] = "Calibri"; const char current_system_divelist_default_font[] = "Segoe UI"; const char *system_divelist_default_font = non_standard_system_divelist_default_font; double system_divelist_default_font_size = -1; void subsurface_OS_pref_setup(void) { if (isWin7Or8()) system_divelist_default_font = current_system_divelist_default_font; } bool subsurface_ignore_font(const char *font) { // if this is running on a recent enough version of Windows and the font // passed in is the pre 4.3 default font, ignore it if (isWin7Or8() && strcmp(font, non_standard_system_divelist_default_font) == 0) return true; return false; } #define utf8_to_utf16(s) utf8_to_utf16_fl(s, __FILE__, __LINE__) /* '\' not included at the end. */ const char *system_default_directory(void) { static std::string path = utf16_to_utf8(system_default_path()); return path.c_str(); } const char *system_default_filename(void) { static std::string path = utf16_to_utf8(make_default_filename()); return path.c_str(); } int enumerate_devices(device_callback_t callback, void *userdata, unsigned int transport) { int index = -1; DWORD i; if (transport & DC_TRANSPORT_SERIAL) { // Open the registry key. HKEY hKey; LONG rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &hKey); if (rc != ERROR_SUCCESS) { return -1; } // Get the number of values. DWORD count = 0; rc = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &count, NULL, NULL, NULL, NULL); if (rc != ERROR_SUCCESS) { RegCloseKey(hKey); return -1; } for (i = 0; i < count; ++i) { // Get the value name, data and type. char name[512], data[512]; DWORD name_len = sizeof(name); DWORD data_len = sizeof(data); DWORD type = 0; rc = RegEnumValue(hKey, i, name, &name_len, NULL, &type, (LPBYTE)data, &data_len); if (rc != ERROR_SUCCESS) { RegCloseKey(hKey); return -1; } // Ignore non-string values. if (type != REG_SZ) continue; // Prevent a possible buffer overflow. if (data_len >= sizeof(data)) { RegCloseKey(hKey); return -1; } // Null terminate the string. data[data_len] = 0; callback(data, userdata); index++; if (is_default_dive_computer_device(name)) index = i; } RegCloseKey(hKey); } if (transport & DC_TRANSPORT_USBSTORAGE) { int i; int count_drives = 0; const int bufdef = 512; const char *dlabels[] = {"UEMISSDA", "GARMIN", NULL}; char bufname[bufdef], bufval[bufdef], *p; DWORD bufname_len; /* add drive letters that match labels */ memset(bufname, 0, bufdef); bufname_len = bufdef; if (GetLogicalDriveStringsA(bufname_len, bufname)) { p = bufname; while (*p) { memset(bufval, 0, bufdef); if (GetVolumeInformationA(p, bufval, bufdef, NULL, NULL, NULL, NULL, 0)) { for (i = 0; dlabels[i] != NULL; i++) if (!strcmp(bufval, dlabels[i])) { char data[512]; snprintf(data, sizeof(data), "%s (%s)", p, dlabels[i]); callback(data, userdata); if (is_default_dive_computer_device(p)) index = count_drives; count_drives++; } } p = &p[strlen(p) + 1]; } if (count_drives == 1) /* we found exactly one Uemis "drive" */ index = 0; /* make it the selected "device" */ } } return index; } /* bellow we provide a set of wrappers for some I/O functions to use wchar_t. * on win32 this solves the issue that we need paths to be utf-16 encoded. */ int subsurface_rename(const char *path, const char *newpath) { if (!path || !newpath) return -1; std::wstring wpath = utf8_to_utf16(path); std::wstring wnewpath = utf8_to_utf16(newpath); if (!wpath.empty() && !wnewpath.empty()) return _wrename(wpath.c_str(), wnewpath.c_str()); return -1; } // if the QDir based rename fails, we try this one int subsurface_dir_rename(const char *path, const char *newpath) { // check if the folder exists BOOL exists = FALSE; DWORD attrib = GetFileAttributes(path); if (attrib != INVALID_FILE_ATTRIBUTES && attrib & FILE_ATTRIBUTE_DIRECTORY) exists = TRUE; if (!exists && verbose) { report_info("folder not found or path is not a folder: %s", path); return EXIT_FAILURE; } // list of error codes: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx DWORD errorCode; // if this fails something has already obatained (more) exclusive access to the folder HANDLE h = CreateFile(path, GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); if (h == INVALID_HANDLE_VALUE) { errorCode = GetLastError(); if (verbose) report_info("cannot obtain exclusive write access for folder: %u", (unsigned int)errorCode ); return EXIT_FAILURE; } else { if (verbose) report_info("exclusive write access obtained...closing handle!"); CloseHandle(h); // attempt to rename BOOL result = MoveFile(path, newpath); if (!result) { errorCode = GetLastError(); if (verbose) report_info("rename failed: %u", (unsigned int)errorCode); return EXIT_FAILURE; } if (verbose > 1) report_info("folder rename success: %s ---> %s", path, newpath); } return EXIT_SUCCESS; } int subsurface_open(const char *path, int oflags, mode_t mode) { if (!path) return -1; std::wstring wpath = utf8_to_utf16(path); if (!wpath.empty()) return _wopen(wpath.c_str(), oflags, mode); return -1; } FILE *subsurface_fopen(const char *path, const char *mode) { if (!path) return NULL; std::wstring wpath = utf8_to_utf16(path); if (!wpath.empty()) { const int len = strlen(mode); std::wstring wmode(len, ' '); for (int i = 0; i < len; i++) wmode[i] = (wchar_t)mode[i]; return _wfopen(wpath.c_str(), wmode.c_str()); } return NULL; } /* here we return a void pointer instead of _WDIR or DIR pointer */ void *subsurface_opendir(const char *path) { if (!path) return NULL; std::wstring wpath = utf8_to_utf16(path); if (!wpath.empty()) return (void *)_wopendir(wpath.c_str()); return NULL; } int subsurface_access(const char *path, int mode) { if (!path) return -1; std::wstring wpath = utf8_to_utf16(path); if (!wpath.empty()) return _waccess(wpath.c_str(), mode); return -1; } int subsurface_stat(const char* path, struct stat* buf) { if (!path) return -1; std::wstring wpath = utf8_to_utf16(path); if (!wpath.empty()) return wstat(wpath.c_str(), buf); return -1; } #ifndef O_BINARY #define O_BINARY 0 #endif struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) { #if defined(LIBZIP_VERSION_MAJOR) /* libzip 0.10 has zip_fdopen, let's use it since zip_open doesn't have a * wchar_t version */ int fd = subsurface_open(path, O_RDONLY | O_BINARY, 0); struct zip *ret = zip_fdopen(fd, flags, errorp); if (!ret) close(fd); return ret; #else return zip_open(path, flags, errorp); #endif } int subsurface_zip_close(struct zip *zip) { return zip_close(zip); } /* win32 console */ #ifndef WIN32_CONSOLE_APP static struct { bool allocated; UINT cp; FILE *out, *err; } console_desc; #endif void subsurface_console_init(void) { /* if this is a console app already, do nothing */ #ifndef WIN32_CONSOLE_APP /* just in case of multiple calls */ memset((void *)&console_desc, 0, sizeof(console_desc)); /* if AttachConsole(ATTACH_PARENT_PROCESS) returns true the parent process * is a terminal. based on the result, either redirect to that terminal or * to log files. */ console_desc.allocated = AttachConsole(ATTACH_PARENT_PROCESS); if (console_desc.allocated) { console_desc.cp = GetConsoleCP(); SetConsoleOutputCP(CP_UTF8); /* make the ouput utf8 */ console_desc.out = freopen("CON", "w", stdout); console_desc.err = freopen("CON", "w", stderr); } else { verbose = 1; /* set the verbose level to '1' */ std::wstring path = system_default_path(); std::wstring wpath_out = path + L"\\subsurface_out.log"; std::wstring wpath_err = path + L"\\subsurface_err.log"; console_desc.out = _wfreopen(wpath_out.c_str(), L"w", stdout); console_desc.err = _wfreopen(wpath_err.c_str(), L"w", stderr); } puts(""); /* add an empty line */ #endif } void subsurface_console_exit(void) { #ifndef WIN32_CONSOLE_APP /* close handles */ if (console_desc.out) fclose(console_desc.out); if (console_desc.err) fclose(console_desc.err); /* reset code page and free */ if (console_desc.allocated) { SetConsoleOutputCP(console_desc.cp); FreeConsole(); } #endif } bool subsurface_user_is_root() { /* FIXME: Detect admin rights */ return false; } }