mirror of https://github.com/vitalif/openscad
Merge pull request #826 from tim-caper/winconsole.c
use MS Windows API for Unicode and/or stdout supportmaster
commit
aac577635a
248
src/winconsole.c
248
src/winconsole.c
|
@ -8,9 +8,15 @@
|
|||
|
||||
The .com version is a 'wrapper' for the .exe version. If you call
|
||||
'openscad' with no extension from a script or shell, the .com version
|
||||
is prioritized by the OS and feeds the GUI stdout to the console. We use
|
||||
pure C to minimize binary size when cross-compiling (~10kbytes). See Also:
|
||||
is prioritized by the OS and feeds the GUI stdout to the console.
|
||||
We keep it in 'pure c' to minimize binary size on the cross-compile.
|
||||
|
||||
Note the .com file is not a 'real' .com file. It is just an .exe
|
||||
that has been renamed to a .com during the Windows(TM) OpenSCAD package build.
|
||||
|
||||
See Also:
|
||||
|
||||
../doc/windows_issues.txt
|
||||
http://stackoverflow.com/questions/493536/can-one-executable-be-both-a-console-and-gui-app
|
||||
http://blogs.msdn.com/b/oldnewthing/archive/2009/01/01/9259142.aspx
|
||||
http://blogs.msdn.com/b/junfeng/archive/2004/02/06/68531.aspx
|
||||
|
@ -19,93 +25,183 @@
|
|||
Open Group popen() documentation
|
||||
inkscapec by Jos Hirth work at http://kaioa.com
|
||||
Nop Head's OpenSCAD_cl at github.com
|
||||
ImageMagick's utilities, like convert.cc
|
||||
http://www.i18nguy.com/unicode/c-unicode.html
|
||||
|
||||
TODO:
|
||||
Work with unicode: http://www.i18nguy.com/unicode/c-unicode.html
|
||||
A few other notes:
|
||||
|
||||
We throw out argc/argv and pull the w_char commandline using special
|
||||
Win(TM) functions. We then strip out the 'openscad' program name, but
|
||||
leave the rest of the commandline in tact in a tail. Then we call
|
||||
'openscad.exe'.
|
||||
|
||||
stderr is used to support writing of STL/PNG etc. output to stdout.
|
||||
MS Windows API used instead of popen() to append stdout to stderr and
|
||||
avoid running cmd.exe (so save some resources).
|
||||
|
||||
TODO:
|
||||
|
||||
Fix printing of unicode on console.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
/*#include <stdio.h>*/
|
||||
#include <string.h>
|
||||
|
||||
// want to use system definitions instead like #include <system.h>
|
||||
#ifndef WIFEXITED
|
||||
#define WIFEXITED(S) (((S) & 0xff) == 0)
|
||||
#endif
|
||||
#ifndef WEXITSTATUS
|
||||
#define WEXITSTATUS(S) (((S) >> 8) & 0xff)
|
||||
#endif
|
||||
|
||||
#define MAXCMDLEN /*64000*/ 32768 /* MS Windows limit */
|
||||
#define BUFFSIZE 42
|
||||
|
||||
int main( int argc, char * argv[] )
|
||||
{
|
||||
FILE *cmd_stdout;
|
||||
char cmd[MAXCMDLEN];
|
||||
char buffer[BUFFSIZE];
|
||||
int pclose_result;
|
||||
int i;
|
||||
int result = 0;
|
||||
unsigned n; // total number of characters in cmd
|
||||
static const char exe_str[] = "openscad.exe";
|
||||
static const char redirect_str[] = " 2>&1"; // capture stderr and stdout
|
||||
|
||||
memcpy(cmd, exe_str, (n = sizeof(exe_str)-1)); // without \0
|
||||
for ( i = 1 ; i < argc ; ++i ) {
|
||||
register char *s;
|
||||
/*bool*/ int quote;
|
||||
|
||||
cmd[n++] = ' ';
|
||||
// MS Windows special characters need quotation
|
||||
// See issues #440, #441 & #479
|
||||
quote = NULL != strpbrk((s = argv[i]), " \"&'<>^|\t");
|
||||
if (quote) cmd[n++] = '"';
|
||||
while (*s) { // copy & check
|
||||
// The following test is compomise between brevity, clarity and performance.
|
||||
// It could be boiled down to: if ('"' == s[strspn(s,"\\")])
|
||||
// or expanded to avoid repetitive passes of strspn() over same data.
|
||||
if ('"' == *s || ('\\' == *s && '"' == s[strspn(s,"\\")])) cmd[n++] = '\\';
|
||||
cmd[n++] = *s++;
|
||||
if (n >= MAXCMDLEN-sizeof(redirect_str)) {
|
||||
fprintf(stderr, "Command line length exceeds limit of %d\n", MAXCMDLEN);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (quote) cmd[n++] = '"';
|
||||
// manage MS Windows error codes
|
||||
// Do not use fprintf() etc. due to call from thread.
|
||||
static void displayError(char *msg, DWORD errcode) {
|
||||
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
char buffer[1024];
|
||||
if (msg && *msg) {
|
||||
WriteFile(hError, msg, strlen(msg), NULL, NULL);
|
||||
}
|
||||
memcpy(&cmd[n], redirect_str, sizeof(redirect_str)); // including \0
|
||||
if (ERROR_SUCCESS == errcode) return;
|
||||
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errcode, 0,
|
||||
buffer, sizeof(buffer), NULL)) {
|
||||
WriteFile(hError, buffer, strlen(buffer), NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
cmd_stdout = _popen( cmd, "rt" );
|
||||
if ( cmd_stdout == NULL ) {
|
||||
fprintf(stderr, "Error opening _popen for command: %s", cmd );
|
||||
perror( "Error message" );
|
||||
static void displayLastError(char *msg) {
|
||||
displayError(msg, GetLastError());
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
/*volatile*/ int status;
|
||||
HANDLE hRead, hOutput;
|
||||
STARTUPINFOW startupInfo;
|
||||
PROCESS_INFORMATION processInfo;
|
||||
} WATCH_INFO;
|
||||
|
||||
static DWORD WINAPI watchdog(LPVOID arg) {
|
||||
#define info ((WATCH_INFO*)arg)
|
||||
DWORD bc;
|
||||
char buffer[1024];
|
||||
for (info->status=0;;) {
|
||||
if (!ReadFile(info->hRead,buffer,sizeof(buffer),&bc,NULL)) {
|
||||
displayLastError("Failed to read pipe\n");
|
||||
/* return 1; */ info->status = 1;
|
||||
} else if (bc) {
|
||||
(void)WriteFile(info->hOutput, buffer, bc, NULL, NULL);
|
||||
} else break; // closed pipe
|
||||
}
|
||||
return 0;
|
||||
#undef info
|
||||
}
|
||||
|
||||
#define EXE_NAME "openscad.exe"
|
||||
#define ___WIDECHARTEXT(s) L##s
|
||||
#define W(x) ___WIDECHARTEXT(x)
|
||||
|
||||
#define IS_WHITESPACE(c) (' ' == (c) || '\t' == (c))
|
||||
#define MAXCMDLEN 32768 /* MS Windows limit */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
HANDLE hWrite, curr_proc;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
WATCH_INFO info;
|
||||
wchar_t cmd[MAXCMDLEN];
|
||||
DWORD status;
|
||||
int result = 0;
|
||||
register int i;
|
||||
/*bool*/ int quote, preserve_stdout;
|
||||
register wchar_t *cmdline = GetCommandLineW();
|
||||
// Look for the end of executable
|
||||
// There is no need to check for escaped double quotes here
|
||||
// because MS Windows file name can not contain such quotes
|
||||
for (quote=0; *cmdline && (quote || !IS_WHITESPACE(*cmdline)); ++cmdline) {
|
||||
if ('"' == *cmdline) quote ^= 1;
|
||||
}
|
||||
if (IS_WHITESPACE(*cmdline)) {
|
||||
while (IS_WHITESPACE(*(cmdline+1))) ++cmdline;
|
||||
}
|
||||
(void)wcscpy(cmd, W(EXE_NAME));
|
||||
if (wcslen(cmd) + wcslen(cmdline) >= MAXCMDLEN) {
|
||||
// fprintf(stderr, "Command line length exceeds limit of %d\n", MAXCMDLEN);
|
||||
// avoid fprintf() to decrease executable size
|
||||
displayError("Command line length exceeds limit\n", ERROR_SUCCESS);
|
||||
return 1;
|
||||
}
|
||||
(void)wcscat(cmd, cmdline);
|
||||
|
||||
for(;;) {
|
||||
if (NULL == fgets(buffer, BUFFSIZE, cmd_stdout)) {
|
||||
if ( ferror( cmd_stdout ) ) {
|
||||
fprintf(stderr, "Error reading from stdout of %s\n", cmd);
|
||||
result = 1;
|
||||
// look for '-o -' combination
|
||||
for (preserve_stdout=FALSE, i=/*sic!*/2; i<argc; ++i) {
|
||||
register char *s = argv[i];
|
||||
if ('-' == s[0] && '\0' == s[1]) // it is "-"
|
||||
if (!strcmp("-o", argv[i-1])) {
|
||||
preserve_stdout = TRUE; break;
|
||||
}
|
||||
if ( feof( cmd_stdout ) ) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
fputs(buffer, stdout);
|
||||
}
|
||||
|
||||
ZeroMemory(&info, sizeof(info));
|
||||
|
||||
info.startupInfo.cb = sizeof(info.startupInfo);
|
||||
info.startupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
info.startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
info.startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
info.hOutput = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
sa.bInheritHandle = FALSE;
|
||||
if (!CreatePipe(&info.hRead, &hWrite, &sa, 1024)) {
|
||||
displayLastError("CreatePipe(): ");
|
||||
return 1;
|
||||
}
|
||||
// make inheritable write handle(s)
|
||||
curr_proc = GetCurrentProcess();
|
||||
if (!DuplicateHandle(curr_proc, hWrite, curr_proc,
|
||||
&info.startupInfo.hStdError, 0L, TRUE, DUPLICATE_SAME_ACCESS)) {
|
||||
displayLastError("DuplicateHandle(): ");
|
||||
return 1;
|
||||
}
|
||||
if (!preserve_stdout) {
|
||||
info.hOutput = info.startupInfo.hStdOutput;
|
||||
#if 1 // Should I really duplicate handle or plain copy suffice?
|
||||
if (!DuplicateHandle(curr_proc, hWrite, curr_proc,
|
||||
&info.startupInfo.hStdOutput, 0L, TRUE, DUPLICATE_SAME_ACCESS)) {
|
||||
displayLastError("DuplicateHandle(): ");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
info.startupInfo.hStdOutput = info.startupInfo.hStdError;
|
||||
#endif
|
||||
}
|
||||
(void)CloseHandle(hWrite);
|
||||
if (!CreateProcessW(NULL, cmd, NULL, NULL, TRUE,
|
||||
CREATE_UNICODE_ENVIRONMENT, NULL, NULL,
|
||||
&info.startupInfo, &info.processInfo)) {
|
||||
// fwprintf(stderr, L"Cannot run: %s\n", cmd);
|
||||
// avoid fwprintf() to decrease executable size
|
||||
// fputws(L"Cannot run: ", stderr); fputws(cmd, stderr); fputws(L"\n", stderr);
|
||||
displayLastError("Cannot run " EXE_NAME "\n");
|
||||
return 1;
|
||||
}
|
||||
// Create thread to work around ReadFile() blocking
|
||||
if (!CreateThread(NULL, 32768, watchdog, &info, 0, NULL)) {
|
||||
displayLastError("CreateThread(): ");
|
||||
result = 1;
|
||||
}
|
||||
// WaitForSingleObject() returns zero on success
|
||||
if (WaitForSingleObject(info.processInfo.hProcess, INFINITE)) {
|
||||
displayLastError("WaitForSingleObject(): ");
|
||||
result = 1;
|
||||
}
|
||||
if (!GetExitCodeProcess(info.processInfo.hProcess, &status)) {
|
||||
displayLastError("GetExitCodeProcess(): ");
|
||||
result = 1;
|
||||
}
|
||||
if (!result) {
|
||||
if (info.status) result = info.status;
|
||||
else result = status; // return value of openscad.exe
|
||||
}
|
||||
|
||||
pclose_result = _pclose( cmd_stdout );
|
||||
// perror() applicable with return value of -1 only!
|
||||
// Avoid stupid "Error: No Error" message
|
||||
if (pclose_result == -1) {
|
||||
perror("Error while closing stdout for command");
|
||||
result = 1;
|
||||
} else if (!result) {
|
||||
result = WIFEXITED(pclose_result) ? WEXITSTATUS(pclose_result) : 1;
|
||||
}
|
||||
// All currently open streams and handles will be
|
||||
// closed automatically upon the process termination.
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue