mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-01 22:50:31 +00:00
e216f91ebc
Some users try to run Subsurface as root for example to get around permission problems with dive computer devices. This is a bad idea since config files get touched as root and then cannot be read as normal user anymore. This patch allows running as root only with verbose option on. We can assume if somebody manages to start subsurface as root this happens from the command line. For some reason, I couldn't get translation working at this stage. Windows version is a stub. Signed-off-by: Robert C. Helling <helling@atdotde.de> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
454 lines
12 KiB
C
454 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
|
|
}
|
|
|
|
bool subsurface_user_is_root()
|
|
{
|
|
/* FIXME: Detect admin rights */
|
|
return (false);
|
|
}
|