summaryrefslogtreecommitdiffstats
path: root/core/windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/windows.c')
-rw-r--r--core/windows.c454
1 files changed, 454 insertions, 0 deletions
diff --git a/core/windows.c b/core/windows.c
new file mode 100644
index 000000000..58d3beaad
--- /dev/null
+++ b/core/windows.c
@@ -0,0 +1,454 @@
+/* windows.c */
+/* implements Windows specific functions */
+#include <io.h>
+#include "dive.h"
+#include "display.h"
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x500
+#include <windows.h>
+#include <shlobj.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <dirent.h>
+#include <zip.h>
+#include <lmcons.h>
+
+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_user_info(struct user_info *user)
+{ /* Encourage use of at least libgit2-0.20 */ }
+
+extern bool isWin7Or8();
+
+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;
+}
+
+/* this function returns the Win32 Roaming path for the current user as UTF-8.
+ * it never returns NULL but fallsback to .\ instead!
+ * the append argument will append a wchar_t string to the end of the path.
+ */
+static const char *system_default_path_append(const wchar_t *append)
+{
+ wchar_t wpath[MAX_PATH] = { 0 };
+ const char *fname = "system_default_path_append()";
+
+ /* obtain the user path via SHGetFolderPathW.
+ * this API is deprecated but still supported on modern Win32.
+ * fallback to .\ if it fails.
+ */
+ if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, wpath))) {
+ fprintf(stderr, "%s: cannot obtain path!\n", fname);
+ wpath[0] = L'.';
+ wpath[1] = L'\0';
+ }
+
+ wcscat(wpath, L"\\Subsurface");
+ if (append) {
+ wcscat(wpath, L"\\");
+ wcscat(wpath, append);
+ }
+
+ /* attempt to convert the UTF-16 string to UTF-8.
+ * resize the buffer and fallback to .\Subsurface if it fails.
+ */
+ const int wsz = wcslen(wpath);
+ const int sz = WideCharToMultiByte(CP_UTF8, 0, wpath, wsz, NULL, 0, NULL, NULL);
+ char *path = (char *)malloc(sz + 1);
+ if (!sz)
+ goto fallback;
+ if (WideCharToMultiByte(CP_UTF8, 0, wpath, wsz, path, sz, NULL, NULL)) {
+ path[sz] = '\0';
+ return path;
+ }
+
+fallback:
+ fprintf(stderr, "%s: cannot obtain path as UTF-8!\n", fname);
+ const char *local = ".\\Subsurface";
+ const int len = strlen(local) + 1;
+ path = (char *)realloc(path, len);
+ memset(path, 0, len);
+ strcat(path, local);
+ return path;
+}
+
+/* by passing NULL to system_default_path_append() we obtain the pure path.
+ * '\' not included at the end.
+ */
+const char *system_default_directory(void)
+{
+ static const char *path = NULL;
+ if (!path)
+ path = system_default_path_append(NULL);
+ return path;
+}
+
+/* obtain the Roaming path and append "\\<USERNAME>.xml" to it.
+ */
+const char *system_default_filename(void)
+{
+ static wchar_t filename[UNLEN + 5] = { 0 };
+ if (!*filename) {
+ wchar_t username[UNLEN + 1] = { 0 };
+ DWORD username_len = UNLEN + 1;
+ GetUserNameW(username, &username_len);
+ wcscat(filename, username);
+ wcscat(filename, L".xml");
+ }
+ static const char *path = NULL;
+ if (!path)
+ path = system_default_path_append(filename);
+ return path;
+}
+
+int enumerate_devices(device_callback_t callback, void *userdata, int dc_type)
+{
+ int index = -1;
+ DWORD i;
+ if (dc_type != DC_TYPE_UEMIS) {
+ // 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 (dc_type != DC_TYPE_SERIAL) {
+ int i;
+ int count_drives = 0;
+ const int bufdef = 512;
+ const char *dlabels[] = {"UEMISSDA", 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;
+}
+
+/* this function converts a utf-8 string to win32's utf-16 2 byte string.
+ * the caller function should manage the allocated memory.
+ */
+static wchar_t *utf8_to_utf16_fl(const char *utf8, char *file, int line)
+{
+ assert(utf8 != NULL);
+ assert(file != NULL);
+ assert(line);
+ /* estimate buffer size */
+ const int sz = strlen(utf8) + 1;
+ wchar_t *utf16 = (wchar_t *)malloc(sizeof(wchar_t) * sz);
+ if (!utf16) {
+ fprintf(stderr, "%s:%d: %s %d.", file, line, "cannot allocate buffer of size", sz);
+ return NULL;
+ }
+ if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, sz))
+ return utf16;
+ fprintf(stderr, "%s:%d: %s", file, line, "cannot convert string.");
+ free((void *)utf16);
+ return NULL;
+}
+
+#define utf8_to_utf16(s) utf8_to_utf16_fl(s, __FILE__, __LINE__)
+
+/* 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)
+{
+ int ret = -1;
+ if (!path || !newpath)
+ return ret;
+
+ wchar_t *wpath = utf8_to_utf16(path);
+ wchar_t *wnewpath = utf8_to_utf16(newpath);
+
+ if (wpath && wnewpath)
+ ret = _wrename(wpath, wnewpath);
+ free((void *)wpath);
+ free((void *)wnewpath);
+ return ret;
+}
+
+// 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) {
+ fprintf(stderr, "folder not found or path is not a folder: %s\n", 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)
+ fprintf(stderr, "cannot obtain exclusive write access for folder: %u\n", (unsigned int)errorCode );
+ return EXIT_FAILURE;
+ } else {
+ if (verbose)
+ fprintf(stderr, "exclusive write access obtained...closing handle!");
+ CloseHandle(h);
+
+ // attempt to rename
+ BOOL result = MoveFile(path, newpath);
+ if (!result) {
+ errorCode = GetLastError();
+ if (verbose)
+ fprintf(stderr, "rename failed: %u\n", (unsigned int)errorCode);
+ return EXIT_FAILURE;
+ }
+ if (verbose > 1)
+ fprintf(stderr, "folder rename success: %s ---> %s\n", path, newpath);
+ }
+ return EXIT_SUCCESS;
+}
+
+int subsurface_open(const char *path, int oflags, mode_t mode)
+{
+ int ret = -1;
+ if (!path)
+ return ret;
+ wchar_t *wpath = utf8_to_utf16(path);
+ if (wpath)
+ ret = _wopen(wpath, oflags, mode);
+ free((void *)wpath);
+ return ret;
+}
+
+FILE *subsurface_fopen(const char *path, const char *mode)
+{
+ FILE *ret = NULL;
+ if (!path)
+ return ret;
+ wchar_t *wpath = utf8_to_utf16(path);
+ if (wpath) {
+ const int len = strlen(mode);
+ wchar_t wmode[len + 1];
+ for (int i = 0; i < len; i++)
+ wmode[i] = (wchar_t)mode[i];
+ wmode[len] = 0;
+ ret = _wfopen(wpath, wmode);
+ }
+ free((void *)wpath);
+ return ret;
+}
+
+/* here we return a void pointer instead of _WDIR or DIR pointer */
+void *subsurface_opendir(const char *path)
+{
+ _WDIR *ret = NULL;
+ if (!path)
+ return ret;
+ wchar_t *wpath = utf8_to_utf16(path);
+ if (wpath)
+ ret = _wopendir(wpath);
+ free((void *)wpath);
+ return (void *)ret;
+}
+
+int subsurface_access(const char *path, int mode)
+{
+ int ret = -1;
+ if (!path)
+ return ret;
+ wchar_t *wpath = utf8_to_utf16(path);
+ if (wpath)
+ ret = _waccess(wpath, mode);
+ free((void *)wpath);
+ return ret;
+}
+
+#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 */
+static struct {
+ bool allocated;
+ UINT cp;
+ FILE *out, *err;
+} console_desc;
+
+void subsurface_console_init(bool dedicated)
+{
+ (void)console_desc;
+ /* 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));
+ /* the AttachConsole(..) call can be used to determine if the parent process
+ * is a terminal. if it succeeds, there is no need for a dedicated console
+ * window and we don't need to call the AllocConsole() function. on the other
+ * hand if the user has set the 'dedicated' flag to 'true' and if AttachConsole()
+ * has failed, we create a dedicated console window.
+ */
+ console_desc.allocated = AttachConsole(ATTACH_PARENT_PROCESS);
+ if (console_desc.allocated)
+ dedicated = false;
+ if (!console_desc.allocated && dedicated)
+ console_desc.allocated = AllocConsole();
+ if (!console_desc.allocated)
+ return;
+
+ console_desc.cp = GetConsoleCP();
+ SetConsoleOutputCP(CP_UTF8); /* make the ouput utf8 */
+
+ /* set some console modes; we don't need to reset these back.
+ * ENABLE_EXTENDED_FLAGS = 0x0080, ENABLE_QUICK_EDIT_MODE = 0x0040 */
+ HANDLE h_in = GetStdHandle(STD_INPUT_HANDLE);
+ if (h_in) {
+ SetConsoleMode(h_in, 0x0080 | 0x0040);
+ CloseHandle(h_in);
+ }
+
+ /* dedicated only; disable the 'x' button as it will close the main process as well */
+ HWND h_cw = GetConsoleWindow();
+ if (h_cw && dedicated) {
+ SetWindowTextA(h_cw, "Subsurface Console");
+ HMENU h_menu = GetSystemMenu(h_cw, 0);
+ if (h_menu) {
+ EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED);
+ DrawMenuBar(h_cw);
+ }
+ SetConsoleCtrlHandler(NULL, TRUE); /* disable the CTRL handler */
+ }
+
+ /* redirect; on win32, CON is a reserved pipe target, like NUL */
+ console_desc.out = freopen("CON", "w", stdout);
+ console_desc.err = freopen("CON", "w", stderr);
+ if (!dedicated)
+ puts(""); /* add an empty line */
+#endif
+}
+
+void subsurface_console_exit(void)
+{
+#ifndef WIN32_CONSOLE_APP
+ if (!console_desc.allocated)
+ return;
+
+ /* close handles */
+ if (console_desc.out)
+ fclose(console_desc.out);
+ if (console_desc.err)
+ fclose(console_desc.err);
+
+ /* reset code page and free */
+ SetConsoleOutputCP(console_desc.cp);
+ FreeConsole();
+#endif
+}
+
+bool subsurface_user_is_root()
+{
+ /* FIXME: Detect admin rights */
+ return (false);
+}