//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) 2001 - 2008 Twan van Laarhoven and "coppro" | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ /** @file cli/win32_cli_wrapper.cpp * * Windows is stupid with regards to consoles: * - a program is either 'GUI' or 'console' * - gui programs do not inherit the console of the parent * - hence, there is NO way to printf from a gui program to an existing console * * This wrapper hacks around that by opening pipes for std{in,our,err}. * The gui program (MSE) can then use stdout as usual, while this program passes * the text to the real stdout. * * In addition, this wrapping allows us to translate a (subset of) ANSI escape codes * so we can use fancy colors. * */ #ifdef WIN32 // only needed on windows (duh) // ----------------------------------------------------------------------------- : Includes #define UNICODE #define _UNICODE #define _CRT_SECURE_NO_WARNINGS #include #include #include // ----------------------------------------------------------------------------- : Prototypes /// How to transfer data from one handle to another struct Transfer { HANDLE from, &to; bool escapes; }; DWORD WINAPI TransferThread(Transfer*); BOOL WINAPI HandleCtrlEvent(DWORD type); void CopyFileBuffer(HANDLE output, char* buffer, DWORD size); void InitEscapeTranslation(HANDLE console); void PerformEscapeCode(HANDLE console, char command, int argc, int argv[]); /// The child process PROCESS_INFORMATION child_process_info; /// Pipes / console handles HANDLE in_mine, in_theirs, in_real; HANDLE out_mine, out_theirs, out_real; HANDLE err_mine, err_theirs, err_real; // ----------------------------------------------------------------------------- : Main function const char* redirect_flags[] = {"-?","--help","-v","--version","--cli","-c","--export","--create-installer"}; int main(int argc, char** argv) { // determine whether we need to wrap console i/o bool need_redirection = false; for (int i = 1 ; i < argc ; ++i) { for (int j = 0 ; j < sizeof(redirect_flags)/sizeof(redirect_flags[0]) ; ++j) { if (strcmp(argv[i],redirect_flags[j]) == 0) { need_redirection = true; goto break_2; } } } break_2: // command line TCHAR* command_line = GetCommandLine(); if (need_redirection) { // update command line : add --color flag TCHAR* my_command_line = command_line; command_line = new TCHAR[_tcsclen(command_line) + 10]; _tcscpy(command_line, my_command_line); _tcscat(command_line, _T(" --color")); } // application name TCHAR app_path[2048]; GetModuleFileName(NULL/*current process*/, app_path, sizeof(app_path)/sizeof(TCHAR)); size_t app_path_length = _tcsclen(app_path); if (app_path_length > 4 && _tcsicmp(app_path + app_path_length - 4, _T(".com")) == 0) { // replace ".com" with ".exe" _tcscpy(app_path + app_path_length - 4, _T(".exe")); } else { // not a .com file, error message fprintf(stderr, "This executable should be named .com\n"); } // win32 structures for child program STARTUPINFO child_startup_info; memset(&child_startup_info, 0, sizeof(child_startup_info)); memset(&child_process_info, 0, sizeof(child_process_info)); child_startup_info.cb = sizeof(child_startup_info); // setup redirection if (need_redirection) { // Ctrl+C handler SetConsoleCtrlHandler(HandleCtrlEvent, TRUE); // create pipes CreatePipe(&in_theirs, &in_mine, NULL, 0); CreatePipe(&out_mine, &out_theirs, NULL, 0); CreatePipe(&err_mine, &err_theirs, NULL, 0); // the actual handles in_real = GetStdHandle(STD_INPUT_HANDLE); out_real = GetStdHandle(STD_OUTPUT_HANDLE); err_real = GetStdHandle(STD_ERROR_HANDLE); InitEscapeTranslation(out_real); // start threads Transfer tranfer_in = {in_real, in_mine, false}; Transfer tranfer_out = {out_mine, out_real, true}; Transfer tranfer_err = {err_mine, err_real, true}; CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)TransferThread,&tranfer_in, 0,NULL); CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)TransferThread,&tranfer_out,0,NULL); CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)TransferThread,&tranfer_err,0,NULL); // give (inheritable copies of) handles to child process HANDLE me = GetCurrentProcess(); DuplicateHandle(me,in_theirs, me,&child_startup_info.hStdInput, 0,TRUE,DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS); DuplicateHandle(me,out_theirs,me,&child_startup_info.hStdOutput,0,TRUE,DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS); DuplicateHandle(me,err_theirs,me,&child_startup_info.hStdError, 0,TRUE,DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS); child_startup_info.dwFlags = STARTF_USESTDHANDLES; } // start the child program if (!CreateProcess(app_path,command_line,NULL,NULL,TRUE,0,NULL,NULL,&child_startup_info,&child_process_info)) { ExitProcess(1); } // wait for program to terminate DWORD exit_code = 0; if (need_redirection) { delete [] command_line; WaitForSingleObject(child_process_info.hProcess, INFINITE); GetExitCodeProcess(child_process_info.hProcess, &exit_code); } // That's all folks! return exit_code; } // ----------------------------------------------------------------------------- : Terminating /// Handle Ctrl+C BOOL WINAPI HandleCtrlEvent(DWORD type) { DWORD exit_code = 1; // try to exit child process cleanly // TODO: don't exit child on Ctrl+C bool wait = false; if (in_mine != INVALID_HANDLE_VALUE) { CopyFileBuffer(in_mine,":quit\n",6); CopyFileBuffer(out_real,":quit\n",6); wait = true; } if (wait && WaitForSingleObject(child_process_info.hProcess,100) == WAIT_OBJECT_0) { GetExitCodeProcess(child_process_info.hProcess, &exit_code); } else { TerminateProcess(child_process_info.hProcess,1); } // exit this process ExitProcess(exit_code); return TRUE; } // ----------------------------------------------------------------------------- : I/O redirection /// Copy a buffer to an output handle void CopyFileBuffer(HANDLE output, char* buffer, DWORD size) { DWORD pos = 0, bytes_written; while (pos < size) { WriteFile(output, buffer + pos, size - pos, &bytes_written, NULL); pos += bytes_written; } } /// Copy a buffer to an output handle, handling escape code void CopyFileBufferWithEscape(HANDLE output, char* buffer, DWORD size, bool handle_escapes) { if (!handle_escapes) { CopyFileBuffer(output, buffer, size); return; } DWORD pos = 0; while (pos < size) { // find next escape code, "\x1B[" DWORD next_pos = pos; while (next_pos < size && (buffer[next_pos] != '\x1B' || (next_pos + 1 >= size || buffer[next_pos+1] != '['))) ++next_pos; // copy part before next escape CopyFileBuffer(output, buffer+pos, next_pos-pos); pos = next_pos; // is this an escape code? if (pos + 1 < size) { // handle escape code int argv[10] = {0}, argc = 1; for (pos += 2 ; pos < size ; ++pos) { if (buffer[pos] == ';') { ++argc; } else if (buffer[pos] >= '0' && buffer[pos] <= '9') { argv[argc-1] = 10 * argv[argc-1] + buffer[pos] - '0'; } else { PerformEscapeCode(output, buffer[pos], argc, argv); pos++; break; } } } } } /// Thread to transfer text from one handle to another DWORD WINAPI TransferThread(Transfer* transfer) { while (true) { // read char buffer[1024]; DWORD bytes_read; BOOL ok = ReadFile(transfer->from, buffer, sizeof(buffer), &bytes_read, NULL); if (ok && bytes_read == 0) { // end of file: close handle CloseHandle(transfer->to); transfer->to = INVALID_HANDLE_VALUE; break; } // write CopyFileBufferWithEscape(transfer->to, buffer, bytes_read, transfer->escapes); } return 0; } // ----------------------------------------------------------------------------- : Escape codes #define FOREGROUND_COLOR (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) #define BACKGROUND_COLOR (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) WORD original_attributes; /// Initialization for escape translation void InitEscapeTranslation(HANDLE console) { CONSOLE_SCREEN_BUFFER_INFO screen_buffer; GetConsoleScreenBufferInfo(console, &screen_buffer); original_attributes = screen_buffer.wAttributes; } /// Perform an escape code void PerformEscapeCode(HANDLE console, char command, int argc, int argv[]) { switch (command) { case 'm': { CONSOLE_SCREEN_BUFFER_INFO screen_buffer; GetConsoleScreenBufferInfo(console, &screen_buffer); WORD attributes = screen_buffer.wAttributes; for (int i = 0 ; i < argc ; ++i) { int major = argv[i] / 10, minor = argv[i] % 10; if (argv[i] == 0) { // reset attributes = original_attributes; } else if (argv[i] == 1) { // bold attributes |= FOREGROUND_INTENSITY; } else if (argv[i] == 7) { // reverse #if BACKGROUND_RED != FOREGROUND_RED << 4 #error Color codes are not what I expected them to be #endif attributes = (attributes & (FOREGROUND_COLOR | FOREGROUND_INTENSITY)) << 4 | (attributes & (BACKGROUND_COLOR | BACKGROUND_INTENSITY)) >> 4; } else if (argv[i] == 21 || argv[i] == 22) { // not bold attributes &= ~FOREGROUND_INTENSITY; } else if (major == 3) { // foreground color attributes &= ~FOREGROUND_COLOR; if (minor == 9) { // reset attributes |= original_attributes & FOREGROUND_COLOR; } else { attributes |= (minor & 1 ? FOREGROUND_RED : 0) | (minor & 2 ? FOREGROUND_GREEN : 0) | (minor & 4 ? FOREGROUND_BLUE : 0); } } else if (major == 4) { // background color attributes &= ~BACKGROUND_COLOR; if (minor == 9) { // reset attributes |= original_attributes & BACKGROUND_COLOR; } else { attributes |= (minor & 1 ? BACKGROUND_RED : 0) | (minor & 2 ? BACKGROUND_GREEN : 0) | (minor & 4 ? BACKGROUND_BLUE : 0); } } else { // other, ignore } } if (attributes != screen_buffer.wAttributes) { SetConsoleTextAttribute(console, attributes); } break; } default: break; // unsupported command, ignore } } // ----------------------------------------------------------------------------- : EOF #endif // WIN32