/* * Copyright (c) 2005 Alexander Gottwald * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name(s) of the above copyright * holders shall not be used in advertising or otherwise to promote the sale, * use or other dealings in this Software without prior written authorization. */ #include "window/util.h" #include "window/wizard.h" #include "resources/resources.h" #include "config.h" #include #include #include #include #ifdef _MSC_VER #define snprintf _snprintf #endif #include #include /// @brief Send WM_ENDSESSION to all program windows. /// This will shutdown the started xserver BOOL CALLBACK KillWindowsProc(HWND hwnd, LPARAM lParam) { SendMessage(hwnd, WM_ENDSESSION, 0, 0); return TRUE; } /// @brief Actual wizard implementation. /// This is based on generic CWizard but handles the special dialogs class CMyWizard : public CWizard { public: private: CConfig config; /// Storage for config options. public: /// @brief Constructor. /// Set wizard pages. CMyWizard() : CWizard() { AddPage(IDD_DISPLAY, IDS_DISPLAY_TITLE, IDS_DISPLAY_SUBTITLE); AddPage(IDD_CLIENTS, IDS_CLIENTS_TITLE, IDS_CLIENTS_SUBTITLE); AddPage(IDD_PROGRAM, IDS_PROGRAM_TITLE, IDS_PROGRAM_SUBTITLE); AddPage(IDD_XDMCP, IDS_XDMCP_TITLE, IDS_XDMCP_SUBTITLE); //AddPage(IDD_FONTPATH, IDS_FONTPATH_TITLE, IDS_FONTPATH_SUBTITLE); AddPage(IDD_CLIPBOARD, IDS_CLIPBOARD_TITLE, IDS_CLIPBOARD_SUBTITLE); AddPage(IDD_FINISH, IDS_FINISH_TITLE, IDS_FINISH_SUBTITLE); } virtual void LoadConfig(const char *filename) { try { config.Load(filename); } catch (std::runtime_error &e) { printf("Fehler: %s\n", e.what()); } } /// @brief Handle the PSN_WIZNEXT message. /// @param hwndDlg Handle to active page dialog. /// @param index Index of current page. /// @return TRUE if the message was handled. FALSE otherwise. virtual BOOL WizardNext(HWND hwndDlg, unsigned index) { #ifdef _DEBUG printf("%s %d\n", __FUNCTION__, index); #endif switch (PageID(index)) { case IDD_DISPLAY: // Check for select window mode if (IsDlgButtonChecked(hwndDlg, IDC_MULTIWINDOW)) config.window = CConfig::MultiWindow; else if (IsDlgButtonChecked(hwndDlg, IDC_FULLSCREEN)) config.window = CConfig::Fullscreen; else if (IsDlgButtonChecked(hwndDlg, IDC_WINDOWED)) config.window = CConfig::Windowed; else if (IsDlgButtonChecked(hwndDlg, IDC_NODECORATION)) config.window = CConfig::Nodecoration; else { SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); return TRUE; } // Get selected display number { char buffer[512]; GetDlgItemText(hwndDlg, IDC_DISPLAY, buffer, 512); buffer[511] = 0; config.display = buffer; } // Check for valid input if (config.display.empty()) SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); else SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIENTS); return TRUE; case IDD_CLIENTS: // Check for select client startup method if (IsDlgButtonChecked(hwndDlg, IDC_CLIENT)) { config.client = CConfig::StartProgram; SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_PROGRAM); } else if (IsDlgButtonChecked(hwndDlg, IDC_XDMCP)) { config.client = CConfig::XDMCP; SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_XDMCP); } else if (IsDlgButtonChecked(hwndDlg, IDC_CLIENT_NONE)) { config.client = CConfig::NoClient; SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIPBOARD); } else SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); return TRUE; case IDD_PROGRAM: // Check wether local or remote client should be started if (IsDlgButtonChecked(hwndDlg, IDC_CLIENT_LOCAL)) config.local = true; else if (IsDlgButtonChecked(hwndDlg, IDC_CLIENT_REMOTE)) config.local = false; else { SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); return TRUE; } // Read program, user and host name { char buffer[512]; GetDlgItemText(hwndDlg, IDC_CLIENT_USER, buffer, 512); buffer[511] = 0; config.user = buffer; GetDlgItemText(hwndDlg, IDC_CLIENT_HOST, buffer, 512); buffer[511] = 0; config.host = buffer; GetDlgItemText(hwndDlg, IDC_CLIENT_PROGRAM, buffer, 512); buffer[511] = 0; config.program = buffer; } // Check for valid input if (!config.local && (config.host.empty() || config.program.empty())) SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); else SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIPBOARD); return TRUE; case IDD_XDMCP: // Check for broadcast if (IsDlgButtonChecked(hwndDlg, IDC_XDMCP_BROADCAST)) config.broadcast = true; else if (IsDlgButtonChecked(hwndDlg, IDC_XDMCP_QUERY)) config.broadcast = false; else { SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); return TRUE; } // Check for indirect mode if (IsDlgButtonChecked(hwndDlg, IDC_XDMCP_INDIRECT)) config.indirect = true; else config.indirect = false; // Read hostname { char buffer[512]; GetDlgItemText(hwndDlg, IDC_XDMCP_HOST, buffer, 512); buffer[511] = 0; config.xdmcp_host = buffer; } // Check for valid input if (!config.broadcast && config.xdmcp_host.empty()) SetWindowLong(hwndDlg, DWL_MSGRESULT, -1); else SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIPBOARD); return TRUE; case IDD_CLIPBOARD: // check for clipboard if (IsDlgButtonChecked(hwndDlg, IDC_CLIPBOARD)) config.clipboard = true; else config.clipboard = false; // read parameters { char buffer[512]; GetDlgItemText(hwndDlg, IDC_EXTRA_PARAMS, buffer, 512); buffer[511] = 0; config.extra_params = buffer; } SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_FINISH); return TRUE; default: break; } return FALSE; } /// @brief Handle PSN_WIZFINISH message. /// @param hwndDlg Handle to active page dialog. /// @param index Index of current page. /// @return TRUE if the message was handled. FALSE otherwise. virtual BOOL WizardFinish(HWND hwndDlg, unsigned index) { #ifdef _DEBUG printf("finish %d\n", index); #endif return FALSE; } /// @brief Handle PSN_WIZBACK message. /// Basicly handles switching to proper page (skipping XDMCP or program page /// if required). /// @param hwndDlg Handle to active page dialog. /// @param index Index of current page. /// @return TRUE if the message was handled. FALSE otherwise. virtual BOOL WizardBack(HWND hwndDlg, unsigned index) { switch (PageID(index)) { case IDD_PROGRAM: case IDD_XDMCP: SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIENTS); return TRUE; case IDD_FONTPATH: case IDD_CLIPBOARD: // temporary. fontpath is disabled switch (config.client) { case CConfig::NoClient: SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIENTS); return TRUE; case CConfig::StartProgram: SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_PROGRAM); return TRUE; case CConfig::XDMCP: SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_XDMCP); return TRUE; } break; } return FALSE; } /// @brief Handle PSN_SETACTIVE message. /// @param hwndDlg Handle to active page dialog. /// @param index Index of current page. /// @return TRUE if the message was handled. FALSE otherwise. virtual BOOL WizardActivate(HWND hwndDlg, unsigned index) { #ifdef _DEBUG printf("%s %d\n", __FUNCTION__, index); #endif switch (PageID(index)) { case IDD_CLIENTS: // Enable or disable XDMCP radiobutton and text EnableWindow(GetDlgItem(hwndDlg, IDC_XDMCP), config.window != CConfig::MultiWindow); EnableWindow(GetDlgItem(hwndDlg, IDC_XDMCP_DESC), config.window != CConfig::MultiWindow); break; } return FALSE; } protected: /// @brief Enable or disable the control for remote clients. /// @param hwndDlg Handle to active page dialog. /// @param state State of control group. void EnableRemoteProgramGroup(HWND hwndDlg, BOOL state) { EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_PROTOCOL), state); EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_HOST), state); EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_USER), state); EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_PROTOCOL_DESC), state); EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_HOST_DESC), state); EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_USER_DESC), state); } /// @brief Enable or disable the control for XDMCP connection. /// @param hwndDlg Handle to active page dialog. /// @param state State of control group. void EnableXDMCPQueryGroup(HWND hwndDlg, BOOL state) { EnableWindow(GetDlgItem(hwndDlg, IDC_XDMCP_HOST), state); EnableWindow(GetDlgItem(hwndDlg, IDC_XDMCP_INDIRECT), state); } /// @brief Fill program box with default values. /// @param hwndDlg Handle to active page dialog. void FillProgramBox(HWND hwndDlg) { HWND cbwnd = GetDlgItem(hwndDlg, IDC_CLIENT_PROGRAM); if (cbwnd == NULL) return; SendMessage(cbwnd, CB_RESETCONTENT, 0, 0); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "xterm"); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "startkde"); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "gnome-session"); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) ".xinitrc"); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "wmaker"); SendMessage(cbwnd, CB_SETCURSEL, 0, 0); } /// @brief Fill protocol box with default values. /// @param hwndDlg Handle to active page dialog. void FillProtocolBox(HWND hwndDlg) { HWND cbwnd = GetDlgItem(hwndDlg, IDC_CLIENT_PROTOCOL); if (cbwnd == NULL) return; SendMessage(cbwnd, CB_RESETCONTENT, 0, 0); SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "Putty"); //SendMessage(cbwnd, CB_ADDSTRING, 0, (LPARAM) "OpenSSH"); SendMessage(cbwnd, CB_SETCURSEL, 0, 0); } void ShowSaveDialog(HWND parent) { char szTitle[512]; char szFilter[512]; char szFileTitle[512]; char szFile[MAX_PATH]; HINSTANCE hInst = GetModuleHandle(NULL); LoadString(hInst, IDS_SAVE_TITLE, szTitle, sizeof(szTitle)); LoadString(hInst, IDS_SAVE_FILETITLE, szFileTitle, sizeof(szFileTitle)); LoadString(hInst, IDS_SAVE_FILTER, szFilter, sizeof(szFilter)); for (unsigned i=0; szFilter[i]; i++) if (szFilter[i] == '%') szFilter[i] = '\0'; strcpy(szFile, "config.xlaunch"); OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = parent; ofn.lpstrFilter = szFilter; ofn.lpstrFile= szFile; ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = (LPSTR)NULL; ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; ofn.lpstrTitle = szTitle; if (GetSaveFileName(&ofn)) { try { config.Save(ofn.lpstrFile); } catch (std::runtime_error &e) { printf("Fehler: %s\n", e.what()); } } } public: /// @brief Handle messages fo the dialog pages. /// @param hwndDlg Handle of active dialog. /// @param uMsg Message code. /// @param wParam Message parameter. /// @param lParam Message parameter. /// @param psp Handle to sheet paramters. virtual INT_PTR PageDispatch(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, PROPSHEETPAGE *psp) { HWND hwnd; switch (uMsg) { case WM_INITDIALOG: switch (PageID(PageIndex(psp))) { case IDD_DISPLAY: // Init display dialog. Enable correct check buttons switch (config.window) { default: case CConfig::MultiWindow: CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, IDC_MULTIWINDOW); break; case CConfig::Fullscreen: CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, IDC_FULLSCREEN); break; case CConfig::Windowed: CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, IDC_WINDOWED); break; case CConfig::Nodecoration: CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, IDC_NODECORATION); break; } // Set display number SetDlgItemText(hwndDlg, IDC_DISPLAY, config.display.c_str()); break; case IDD_CLIENTS: // Init client dialog. Enable correct check buttons switch (config.client) { default: case CConfig::NoClient: CheckRadioButton(hwndDlg, IDC_CLIENT_NONE, IDC_CLIENT, IDC_CLIENT_NONE); break; case CConfig::StartProgram: CheckRadioButton(hwndDlg, IDC_CLIENT_NONE, IDC_CLIENT, IDC_CLIENT); break; case CConfig::XDMCP: CheckRadioButton(hwndDlg, IDC_CLIENT_NONE, IDC_CLIENT, IDC_XDMCP); break; } break; case IDD_PROGRAM: // Init program dialog. Check local and remote buttons CheckRadioButton(hwndDlg, IDC_CLIENT_LOCAL, IDC_CLIENT_REMOTE, config.local?IDC_CLIENT_LOCAL:IDC_CLIENT_REMOTE); EnableRemoteProgramGroup(hwndDlg, config.local?FALSE:TRUE); // Fill combo boxes FillProgramBox(hwndDlg); FillProtocolBox(hwndDlg); // Set edit fields if (!config.program.empty()) SetDlgItemText(hwndDlg, IDC_CLIENT_PROGRAM, config.program.c_str()); SetDlgItemText(hwndDlg, IDC_CLIENT_USER, config.user.c_str()); SetDlgItemText(hwndDlg, IDC_CLIENT_HOST, config.host.c_str()); break; case IDD_XDMCP: // Init XDMCP dialog. Check broadcast and indirect button CheckRadioButton(hwndDlg, IDC_XDMCP_QUERY, IDC_XDMCP_BROADCAST, config.broadcast?IDC_XDMCP_BROADCAST:IDC_XDMCP_QUERY); CheckDlgButton(hwndDlg, IDC_XDMCP_INDIRECT, config.indirect?BST_CHECKED:BST_UNCHECKED); EnableXDMCPQueryGroup(hwndDlg, config.broadcast?FALSE:TRUE); // Set hostname SetDlgItemText(hwndDlg, IDC_XDMCP_HOST, config.xdmcp_host.c_str()); break; case IDD_CLIPBOARD: CheckDlgButton(hwndDlg, IDC_CLIPBOARD, config.clipboard?BST_CHECKED:BST_UNCHECKED); SetDlgItemText(hwndDlg, IDC_EXTRA_PARAMS, config.extra_params.c_str()); break; } case WM_COMMAND: // Handle control messages switch (LOWORD(wParam)) { // Handle clicks on images. Check proper radiobutton case IDC_MULTIWINDOW_IMG: case IDC_FULLSCREEN_IMG: case IDC_WINDOWED_IMG: case IDC_NODECORATION_IMG: CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, LOWORD(wParam)-4); SetFocus(GetDlgItem(hwndDlg, LOWORD(wParam)-4)); break; // Disable unavailable controls case IDC_CLIENT_REMOTE: case IDC_CLIENT_LOCAL: EnableRemoteProgramGroup(hwndDlg, LOWORD(wParam) == IDC_CLIENT_REMOTE); break; case IDC_XDMCP_QUERY: case IDC_XDMCP_BROADCAST: EnableXDMCPQueryGroup(hwndDlg, LOWORD(wParam) == IDC_XDMCP_QUERY); break; case IDC_FINISH_SAVE: ShowSaveDialog(hwndDlg); break; } } // pass messages to parent return CWizard::PageDispatch(hwndDlg, uMsg, wParam, lParam, psp); } /// @brief Try to connect to server. /// Repeat until successful, server died or maximum number of retries /// reached. Display *WaitForServer(HANDLE serverProcess) { int ncycles = 120; /* # of cycles to wait */ int cycles; /* Wait cycle count */ Display *xd; for (cycles = 0; cycles < ncycles; cycles++) { if ((xd = XOpenDisplay(NULL))) { return xd; } else { if (WaitForSingleObject(serverProcess, 1000) == WAIT_TIMEOUT) continue; } } return NULL; } /// @brief Do the actual start of VCXsrv and clients void StartUp() { std::string buffer; std::string client; // Construct display strings std::string display_id = ":" + config.display; std::string display = "localhost" + display_id + ":0"; #ifdef _DEBUG // Debug only: Switch to VCXsrv installation directory SetCurrentDirectory("C:\\Programme\\vcxsrv"); #endif // Build Xsrv commandline buffer = "vcxsrv " + display_id + " "; switch (config.window) { case CConfig::MultiWindow: buffer += "-multiwindow "; break; case CConfig::Fullscreen: buffer += "-fullscreen "; break; case CConfig::Nodecoration: buffer += "-nodecoration "; break; default: break; } // Add XDMCP parameter if (config.client == CConfig::XDMCP) { if (config.broadcast) buffer += "-broadcast "; else { if (config.indirect) buffer += "-indirect "; else buffer += "-query "; buffer += config.xdmcp_host; buffer += " "; } } if (config.clipboard) buffer += "-clipboard "; if (!config.extra_params.empty()) { buffer += config.extra_params; buffer += " "; } // Construct client commandline if (config.client == CConfig::StartProgram) { if (!config.local) { char cmdline[512]; std::string host = config.host; if (!config.user.empty()) host = config.user + "@" + config.host; if (config.protocol == "Putty") snprintf(cmdline,512,"plink -X %s %s", host.c_str(),config.program.c_str()); else snprintf(cmdline,512,"ssh -Y %s %s", host.c_str(),config.program.c_str()); client += cmdline; } else client += config.program.c_str(); } // Prepare program startup STARTUPINFO si, sic; PROCESS_INFORMATION pi, pic; HANDLE handles[2]; DWORD hcount = 0; Display *dpy = NULL; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); ZeroMemory( &sic, sizeof(sic) ); sic.cb = sizeof(sic); ZeroMemory( &pic, sizeof(pic) ); // Start VCXsrv process. #ifdef _DEBUG printf("%s\n", buffer.c_str()); #endif if( !CreateProcess( NULL, (CHAR*)buffer.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi )) throw win32_error("CreateProcess failed"); handles[hcount++] = pi.hProcess; if (!client.empty()) { // Set DISPLAY variable SetEnvironmentVariable("DISPLAY",display.c_str()); // Wait for server to startup dpy = WaitForServer(pi.hProcess); if (dpy == NULL) { while (hcount--) TerminateProcess(handles[hcount], (DWORD)-1); throw std::runtime_error("Connection to server failed"); } #ifdef _DEBUG printf("%s\n", client.c_str()); #endif // Hide a console window // FIXME: This may make it impossible to enter the password sic.dwFlags = STARTF_USESHOWWINDOW; sic.wShowWindow = SW_HIDE; // Start the child process. if( !CreateProcess( NULL, (CHAR*)client.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &sic, &pic )) { DWORD err = GetLastError(); while (hcount--) TerminateProcess(handles[hcount], (DWORD)-1); throw win32_error("CreateProcess failed", err); } handles[hcount++] = pic.hProcess; } // Wait until any child process exits. DWORD ret = WaitForMultipleObjects(hcount, handles, FALSE, INFINITE ); #ifdef _DEBUG printf("killing process!\n"); #endif // Check if Xsrv is still running DWORD exitcode; GetExitCodeProcess(pi.hProcess, &exitcode); unsigned counter = 0; while (exitcode == STILL_ACTIVE) { if (++counter > 10) TerminateProcess(pi.hProcess, (DWORD)-1); else // Shutdown Xsrv (the soft way!) EnumThreadWindows(pi.dwThreadId, KillWindowsProc, 0); Sleep(500); GetExitCodeProcess(pi.hProcess, &exitcode); } // Kill the client TerminateProcess(pic.hProcess, (DWORD)-1); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); CloseHandle( pic.hProcess ); CloseHandle( pic.hThread ); } }; int main(int argc, char **argv) { try { InitCommonControls(); CMyWizard dialog; bool skip_wizard = false; for (int i = 1; i < argc; i++) { if (argv[i] == NULL) continue; std::string arg(argv[i]); if (arg == "-load" && i + 1 < argc) { i++; dialog.LoadConfig(argv[i]); continue; } if (arg == "-run" && i + 1 < argc) { i++; dialog.LoadConfig(argv[i]); skip_wizard = true; continue; } } int ret = 0; if (skip_wizard || (ret =dialog.ShowModal()) != 0) dialog.StartUp(); #ifdef _DEBUG printf("return %d\n", ret); #endif return 0; } catch (std::runtime_error &e) { printf("Fehler: %s\n", e.what()); return -1; } } #ifdef _MSC_VER #pragma warning(disable:4273) #endif _Check_return_opt_ int __cdecl printf(_In_z_ _Printf_format_string_ const char * pFmt, ...) { static int ConsoleCreated=0; va_list arglist; if (!ConsoleCreated) { int hConHandle; long lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; const unsigned int MAX_CONSOLE_LINES = 500; ConsoleCreated=1; if (!AttachConsole(ATTACH_PARENT_PROCESS)) AllocConsole(); // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); // redirect unbuffered STDOUT to the console lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stdout = *fp; setvbuf( stdout, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; setvbuf( stdin, NULL, _IONBF, 0 ); // redirect unbuffered STDERR to the console lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stderr = *fp; setvbuf( stderr, NULL, _IONBF, 0 ); } va_start(arglist, pFmt ); return vfprintf(stderr, pFmt, arglist); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int argc=1; #define MAXNRARGS 20 char *argv[MAXNRARGS]={"plink"}; char *pTmp=lpCmdLine; while (*pTmp && argc