Edited some module descriptions
[deark.git] / src / deark-win.c
blob61ee4399cac103fe7e7f49e50eff9dcb79ba7e81
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // Functions specific to Microsoft Windows, especially those that require
6 // windows.h.
8 #define DE_NOT_IN_MODULE
9 #include "deark-config.h"
11 #ifdef DE_WINDOWS
13 #include <windows.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
18 // This file is overloaded, in that it contains functions intended to only
19 // be used internally, as well as functions intended only for the
20 // command-line utility. That's why we need both deark-user.h and
21 // deark-private.h.
22 #include "deark-private.h"
23 #include "deark-user.h"
25 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
26 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
27 #endif
29 // Windows-specific contextual data, mainly for console settings.
30 struct de_platform_data {
31 HANDLE msgs_HANDLE;
32 int msgs_HANDLE_is_console;
33 WORD orig_console_attribs;
34 WORD inverse_console_attribs;
37 void de_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap)
39 _vsnprintf_s(buf, buflen, _TRUNCATE, fmt, ap);
42 char *de_strdup(deark *c, const char *s)
44 char *s2;
46 s2 = _strdup(s);
47 if(!s2) {
48 de_err(c, "Memory allocation failed");
49 de_fatalerror(c);
50 return NULL;
52 return s2;
55 i64 de_strtoll(const char *string, char **endptr, int base)
57 return _strtoi64(string, endptr, base);
60 void de_utf8_to_oem(deark *c, const char *src, char *dst, size_t dstlen)
62 WCHAR *srcW;
63 int ret;
65 srcW = de_utf8_to_utf16_strdup(c, src);
67 // FIXME: An issue is that WideCharToMultiByte translates some printable
68 // Unicode characters to OEM graphics characters below 0x20. For example, for
69 // CP437, U+000A (LINE FEED) and U+25D9 (INVERSE WHITE CIRCLE) are both
70 // translated to 0x0a.
71 // The printf-like functions we will use on the translated string interpret
72 // bytes below 0x20 as ASCII control characters, so U+25D9 will end up being
73 // misinterpreted as a newline.
74 // I am not sure what to do about this. It might be possible to change the
75 // mode that printf uses, but we at least need newlines to work.
76 // Ideally, we should probably redesign some things so that de_utf8_to_oem()
77 // is not used with strings that contain newlines. But that's a lot of work
78 // for an obscure feature.
79 ret = WideCharToMultiByte(CP_OEMCP, 0, srcW, -1, dst, (int)dstlen, NULL, NULL);
80 if(ret<1) {
81 dst[0]='\0';
84 de_free(c, srcW);
87 static char *de_utf16_to_utf8_strdup(deark *c, const WCHAR *src)
89 char *dst;
90 int dstlen;
91 int ret;
93 // Calculate the size required by the target string.
94 ret = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
95 if(ret<1) return NULL;
97 dstlen = ret;
98 dst = (char*)de_malloc(c, dstlen);
100 ret = WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dstlen, NULL, NULL);
101 if(ret<1) {
102 de_free(c, dst);
103 return NULL;
105 return dst;
108 wchar_t *de_utf8_to_utf16_strdup(deark *c, const char *src)
110 WCHAR *dst;
111 int dstlen;
112 int ret;
114 // Calculate the size required by the target string.
115 ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0);
116 if(ret<1) {
117 de_err(c, "Encoding conversion failed");
118 de_fatalerror(c);
119 return NULL;
122 dstlen = ret;
123 dst = (WCHAR*)de_mallocarray(c, dstlen, sizeof(WCHAR));
125 ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dstlen);
126 if(ret<1) {
127 de_free(c, dst);
128 de_err(c, "Encoding conversion failed");
129 de_fatalerror(c);
130 return NULL;
132 return dst;
135 // Convert a string from utf8 to utf16, then write it to a FILE
136 // (e.g. using fputws).
137 void de_utf8_to_utf16_to_FILE(deark *c, const char *src, FILE *f)
139 #define DST_SMALL_SIZE 1024
140 WCHAR dst_small[DST_SMALL_SIZE];
141 WCHAR *dst_large;
142 int ret;
144 ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, dst_small, DST_SMALL_SIZE);
145 if(ret>=1) {
146 // Our "small" buffer was big enough for the converted string.
147 fputws(dst_small, f);
148 return;
151 // Our "small" buffer was not big enough. Do it the slow way.
152 // (Unfortunately, MultiByteToWideChar doesn't have a way to automatically
153 // tell us the required buffer size in the case that the supplied buffer
154 // was not big enough. So we end up calling it three times, when two should
155 // have been sufficient. But this is a rare code path.)
156 dst_large = de_utf8_to_utf16_strdup(c, src);
157 fputws(dst_large, f);
158 de_free(c, dst_large);
161 static FILE* de_fopenW(deark *c, const WCHAR *fnW, const WCHAR *modeW,
162 char *errmsg, size_t errmsg_len)
164 FILE *f = NULL;
165 errno_t errcode;
167 errcode = _wfopen_s(&f, fnW, modeW);
169 errmsg[0] = '\0';
171 if(errcode!=0) {
172 strerror_s(errmsg, (size_t)errmsg_len, (int)errcode);
173 f=NULL;
175 return f;
178 static int de_examine_file_by_fd(deark *c, int fd, i64 *len,
179 char *errmsg, size_t errmsg_len, unsigned int *returned_flags)
181 struct __stat64 stbuf;
182 int retval = 0;
184 *returned_flags = 0;
186 de_zeromem(&stbuf, sizeof(struct __stat64));
188 if(0 != _fstat64(fd, &stbuf)) {
189 strerror_s(errmsg, (size_t)errmsg_len, errno);
190 goto done;
193 if(!(stbuf.st_mode & _S_IFREG)) {
194 de_strlcpy(errmsg, "Not a regular file", errmsg_len);
195 return 0;
198 *len = (i64)stbuf.st_size;
200 retval = 1;
202 done:
203 return retval;
206 FILE* de_fopen_for_read(deark *c, const char *fn, i64 *len,
207 char *errmsg, size_t errmsg_len, unsigned int *returned_flags)
209 int ret;
210 FILE *f;
211 WCHAR *fnW;
213 fnW = de_utf8_to_utf16_strdup(c, fn);
215 f = de_fopenW(c, fnW, L"rb", errmsg, errmsg_len);
217 de_free(c, fnW);
219 if(!f) {
220 return NULL;
223 ret = de_examine_file_by_fd(c, _fileno(f), len, errmsg, errmsg_len,
224 returned_flags);
225 if(!ret) {
226 de_fclose(f);
227 return NULL;
230 return f;
233 // flags: 0x1 = append instead of overwriting
234 FILE* de_fopen_for_write(deark *c, const char *fn,
235 char *errmsg, size_t errmsg_len, int overwrite_mode,
236 unsigned int flags)
238 const WCHAR *modeW;
239 WCHAR *fnW = NULL;
240 FILE *f_ret = NULL;
242 // A simple check to make it harder to accidentally overwrite the input
243 // file. (But it can easily be defeated.)
244 // TODO?: Make this more robust.
245 if(c->input_filename && !de_strcasecmp(fn, c->input_filename)) {
246 de_err(c, "Refusing to write to %s: Same as input filename", fn);
247 de_fatalerror(c);
248 de_strlcpy(errmsg, "", errmsg_len);
249 goto done;
252 modeW = (flags&0x1) ? L"ab" : L"wb";
253 fnW = de_utf8_to_utf16_strdup(c, fn);
255 if(overwrite_mode==DE_OVERWRITEMODE_NEVER) {
256 DWORD fa = GetFileAttributesW(fnW);
257 if(fa != INVALID_FILE_ATTRIBUTES) {
258 de_strlcpy(errmsg, "Output file already exists", errmsg_len);
259 goto done;
263 f_ret = de_fopenW(c, fnW, modeW, errmsg, errmsg_len);
265 done:
266 de_free(c, fnW);
267 return f_ret;
270 int de_fseek(FILE *fp, i64 offs, int whence)
272 return _fseeki64(fp, (__int64)offs, whence);
275 i64 de_ftell(FILE *fp)
277 return (i64)_ftelli64(fp);
280 int de_fclose(FILE *fp)
282 return fclose(fp);
285 static void update_file_time(dbuf *f)
287 WCHAR *fnW = NULL;
288 HANDLE fh = INVALID_HANDLE_VALUE;
289 i64 ft;
290 FILETIME wrtime;
291 deark *c;
293 if(f->btype!=DBUF_TYPE_OFILE) return;
294 if(!f->fi_copy) return;
295 if(!f->fi_copy->timestamp[DE_TIMESTAMPIDX_MODIFY].is_valid) return;
296 if(!f->name) return;
297 c = f->c;
299 ft = de_timestamp_to_FILETIME(&f->fi_copy->timestamp[DE_TIMESTAMPIDX_MODIFY]);
300 if(ft==0) goto done;
301 fnW = de_utf8_to_utf16_strdup(c, f->name);
302 fh = CreateFileW(fnW, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL,
303 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
304 if(fh==INVALID_HANDLE_VALUE) goto done;
306 wrtime.dwHighDateTime = (DWORD)(((u64)ft)>>32);
307 wrtime.dwLowDateTime = (DWORD)(((u64)ft)&0xffffffffULL);
308 SetFileTime(fh, NULL, NULL, &wrtime);
310 done:
311 if(fh != INVALID_HANDLE_VALUE) {
312 CloseHandle(fh);
314 de_free(c, fnW);
317 void de_update_file_attribs(dbuf *f, u8 preserve_file_times)
319 // [Updating file permissions not implemented on Windows.]
321 if(preserve_file_times) {
322 update_file_time(f);
326 char **de_convert_args_to_utf8(int argc, wchar_t **argvW)
328 int i;
329 char **argvUTF8;
331 argvUTF8 = (char**)de_mallocarray(NULL, argc, sizeof(char*));
333 // Convert parameters to UTF-8
334 for(i=0;i<argc;i++) {
335 argvUTF8[i] = de_utf16_to_utf8_strdup(NULL, argvW[i]);
338 return argvUTF8;
341 void de_free_utf8_args(int argc, char **argv)
343 int i;
345 for(i=0;i<argc;i++) {
346 de_free(NULL, argv[i]);
348 de_free(NULL, argv);
351 struct de_platform_data *de_platformdata_create(void)
353 struct de_platform_data *plctx;
355 plctx = de_malloc(NULL, sizeof(struct de_platform_data));
356 return plctx;
359 void de_platformdata_destroy(struct de_platform_data *plctx)
361 if(!plctx) return;
362 de_free(NULL, plctx);
365 // Set the plctx->msgs_HANDLE field, for later use.
366 // n: 1=stdout, 2=stderr
367 void de_winconsole_init_handle(struct de_platform_data *plctx, int n)
369 DWORD consolemode=0;
370 BOOL b;
372 plctx->msgs_HANDLE = GetStdHandle((n==2)?STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
374 b = GetConsoleMode(plctx->msgs_HANDLE, &consolemode);
375 plctx->msgs_HANDLE_is_console = b ? 1 : 0;
378 // Does plctx->msgs_HANDLE seem to be a Windows console?
379 int de_winconsole_is_console(struct de_platform_data *plctx)
381 return plctx->msgs_HANDLE_is_console;
384 void de_winconsole_set_UTF8_CP(struct de_platform_data *plctx)
386 // I hate to do this, but it's the least-bad fix I've found for some issues
387 // that have cropped up in the wake of Cygwin+Mintty using Windows 10's
388 // ConPTY features (as of Cygwin 3.1.0 - Dec. 2019).
390 // Note that, somewhat ironically, we only change the console code page if
391 // the output is *not* going directly to a console.
393 // Unfortunately, rude as it is not to do so, we can't restore the original
394 // code page settings when we're done. If we restore the code page, we have to
395 // do it after all of the output has reached the console. But if our output
396 // is being piped through a pager, some of it likely won't reach the console
397 // until after our program ends.
398 SetConsoleCP(65001);
399 SetConsoleOutputCP(65001);
402 // Save current attribs to plctx.
403 // Returns 1 on success.
404 void de_winconsole_record_current_attributes(struct de_platform_data *plctx)
406 CONSOLE_SCREEN_BUFFER_INFO csbi;
408 if(GetConsoleScreenBufferInfo(plctx->msgs_HANDLE, &csbi)) {
409 plctx->orig_console_attribs = csbi.wAttributes;
411 else {
412 plctx->orig_console_attribs = 0x0007;
415 plctx->inverse_console_attribs =
416 (plctx->orig_console_attribs&0xff00) |
417 ((plctx->orig_console_attribs&0x000f)<<4) |
418 ((plctx->orig_console_attribs&0x00f0)>>4);
421 // If we think this computer supports 24-bit color ANSI, enable it (if needed)
422 // and return 1.
423 // Otherwise return 0.
424 int de_winconsole_try_enable_ansi24(struct de_platform_data *plctx)
426 // Note: Maybe we should check for Windows 10 (e.g. using
427 // IsWindows10OrGreater()) before calling SetConsoleMode(), but maybe that's
428 // just be a waste of time. Also, IsWindows10OrGreater() is fragile because
429 // it requires a .manifest file with certain properties.
431 // TODO: This is not correct. AFAIK, there is a range of Windows 10 builds
432 // that support ANSI codes, but do not support 24-bit color ANSI.
433 // I don't know a *good* way to detect 24-bit color support.
434 // Querying the Windows build number is possible, but requires some hackery,
435 // because Microsoft *really* does not want applications to do that.
436 return de_winconsole_enable_ansi(plctx);
439 int de_winconsole_enable_ansi(struct de_platform_data *plctx)
441 BOOL b;
442 DWORD oldmode = 0;
444 if(!plctx->msgs_HANDLE_is_console) return 1;
446 b = GetConsoleMode(plctx->msgs_HANDLE, &oldmode);
447 if(!b) return 0;
448 if(oldmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) return 1; // Already enabled
450 // The ENABLE_VIRTUAL_TERMINAL_PROCESSING mode is what enables interpretation
451 // of ANSI escape codes.
453 // Note: This mode seems to be specific to the console window, not to the specific
454 // I/O handle that we pass to SetConsoleMode.
455 // I.e. if both stderr and stdout refer to the console, it doesn't matter which
456 // one we use here.
457 // And if we write an ANSI code to stderr, it could also affect stdout.
458 // That's not what we want, but it shouldn't cause much of a problem for us.
460 // Note: This mode seems to get reset automatically when the process ends.
461 // It doesn't affect future processes that run in the same console.
462 // So we don't have to try to set it back when we're done.
464 b = SetConsoleMode(plctx->msgs_HANDLE, oldmode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
465 if(!b) return 0;
466 return 1;
469 void de_winconsole_highlight(struct de_platform_data *plctx, int x)
471 if(x) {
472 SetConsoleTextAttribute(plctx->msgs_HANDLE, plctx->inverse_console_attribs);
474 else {
475 SetConsoleTextAttribute(plctx->msgs_HANDLE, plctx->orig_console_attribs);
479 // Note: Need to keep this function in sync with the implementation in deark-unix.c.
480 void de_current_time_to_timestamp(struct de_timestamp *ts)
482 FILETIME ft1;
483 i64 ft;
485 GetSystemTimeAsFileTime(&ft1);
486 ft = (i64)(((u64)ft1.dwHighDateTime)<<32 | ft1.dwLowDateTime);
487 de_FILETIME_to_timestamp(ft, ts, 0x1);
490 void de_exitprocess(int s)
492 exit(s);
495 #endif // DE_WINDOWS