mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 13:10:19 +00:00
6cddf1d720
The old backend was less safe because, it didn't: - support UTF-16 paths! - use GetUserNameW() The following changes were made: - system_default_path_append() can be used to retrieve a path in the user Roaming path. - system_default_directory() - this is equal to system_default_path_append(NULL) - system_default_filename() also uses the helper system_default_path_append() Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
448 lines
12 KiB
C
448 lines
12 KiB
C
/* 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
|
|
}
|