diff --git a/NEWS.md b/NEWS.md index f31f5722..ef8fc383 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ Version 0.7.3 - fix: the "add bookmark" menu item is not correctly disabled on a bookmarked line - enhancement: "use utf8 by default" in editor's misc setting - fix: syntax issues not correctly cleared when the file was saved as another name. + - enhancement: when running a program, redirect a data file to its stdin Version 0.7.2 - fix: rainbow parenthesis stop functioning when change editor's general options diff --git a/RedPandaIDE/compiler/compilermanager.cpp b/RedPandaIDE/compiler/compilermanager.cpp index 5d5d2cfa..d549fd04 100644 --- a/RedPandaIDE/compiler/compilermanager.cpp +++ b/RedPandaIDE/compiler/compilermanager.cpp @@ -197,12 +197,26 @@ void CompilerManager::run(const QString &filename, const QString &arguments, con if (mRunner!=nullptr) { return; } + QChar redirectChar = '0'; + QString redirectInputFilename; + bool redirectInput=false; + if (pSettings->executor().redirectInput() + && !pSettings->executor().inputFilename().isEmpty()) { + redirectInput =true; + redirectChar = '1'; + redirectInputFilename = pSettings->executor().inputFilename(); + } if (pSettings->executor().pauseConsole() && programHasConsole(filename)) { - QString newArguments = QString(" 0 \"%1\" %2").arg(toLocalPath(filename)).arg(arguments); + QString newArguments = QString(" %1 \"%2\" %3") + .arg(redirectChar) + .arg(toLocalPath(filename)).arg(arguments); mRunner = new ExecutableRunner(includeTrailingPathDelimiter(pSettings->dirs().app())+"ConsolePauser.exe",newArguments,workDir); + if (redirectInput) + mRunner->setRedirectConsoleProgram(true); } else { mRunner = new ExecutableRunner(filename,arguments,workDir); } + mRunner->setRedirectInputFilename(redirectInputFilename); connect(mRunner, &ExecutableRunner::finished, this ,&CompilerManager::onRunnerTerminated); connect(mRunner, &ExecutableRunner::finished, pMainWindow ,&MainWindow::onRunFinished); connect(mRunner, &ExecutableRunner::runErrorOccurred, pMainWindow ,&MainWindow::onRunErrorOccured); diff --git a/RedPandaIDE/compiler/executablerunner.cpp b/RedPandaIDE/compiler/executablerunner.cpp index 36624938..9323aa61 100644 --- a/RedPandaIDE/compiler/executablerunner.cpp +++ b/RedPandaIDE/compiler/executablerunner.cpp @@ -12,7 +12,8 @@ ExecutableRunner::ExecutableRunner(const QString &filename, const QString &argum mFilename(filename), mArguments(arguments), mWorkDir(workDir), - mStop(false) + mStop(false), + mRedirectConsoleProgram(false) { } @@ -22,6 +23,26 @@ void ExecutableRunner::stop() mStop = true; } +bool ExecutableRunner::redirectConsoleProgram() const +{ + return mRedirectConsoleProgram; +} + +void ExecutableRunner::setRedirectConsoleProgram(bool newRedirectConsoleProgram) +{ + mRedirectConsoleProgram = newRedirectConsoleProgram; +} + +const QString &ExecutableRunner::redirectInputFilename() const +{ + return mRedirectInputFilename; +} + +void ExecutableRunner::setRedirectInputFilename(const QString &newDataFile) +{ + mRedirectInputFilename = newDataFile; +} + void ExecutableRunner::run() { emit started(); @@ -48,19 +69,31 @@ void ExecutableRunner::run() } env.insert("PATH",path); process.setProcessEnvironment(env); - process.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments * args){ - args->flags |= CREATE_NEW_CONSOLE; - args->startupInfo -> dwFlags &= ~STARTF_USESTDHANDLES; - }); + if (redirectConsoleProgram()) { + process.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments * args){ + args->flags |= CREATE_NEW_CONSOLE; + }); + } else { + process.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments * args){ + args->flags |= CREATE_NEW_CONSOLE; + args->startupInfo -> dwFlags &= ~STARTF_USESTDHANDLES; + }); + } process.connect(&process, &QProcess::errorOccurred, [&](){ errorOccurred= true; }); // qDebug() << mFilename; // qDebug() << QProcess::splitCommand(mArguments); + if (!redirectConsoleProgram()) { + process.closeWriteChannel(); + } process.start(); - process.closeWriteChannel(); process.waitForStarted(5000); + if (process.state()==QProcess::Running && redirectConsoleProgram()) { + process.write(ReadFileToByteArray(redirectInputFilename())); + process.closeWriteChannel(); + } while (true) { process.waitForFinished(1000); if (process.state()!=QProcess::Running) { diff --git a/RedPandaIDE/compiler/executablerunner.h b/RedPandaIDE/compiler/executablerunner.h index 20886cb9..856f0fa1 100644 --- a/RedPandaIDE/compiler/executablerunner.h +++ b/RedPandaIDE/compiler/executablerunner.h @@ -9,6 +9,12 @@ class ExecutableRunner : public QThread public: ExecutableRunner(const QString& filename, const QString& arguments, const QString& workDir); + const QString &redirectInputFilename() const; + void setRedirectInputFilename(const QString &newDataFile); + + bool redirectConsoleProgram() const; + void setRedirectConsoleProgram(bool newRedirectConsoleProgram); + signals: void started(); void terminated(); @@ -22,6 +28,8 @@ private: QString mArguments; QString mWorkDir; bool mStop; + QString mRedirectInputFilename; + bool mRedirectConsoleProgram; // QThread interface protected: diff --git a/RedPandaIDE/mainwindow.cpp b/RedPandaIDE/mainwindow.cpp index 9817b8bd..10a24607 100644 --- a/RedPandaIDE/mainwindow.cpp +++ b/RedPandaIDE/mainwindow.cpp @@ -1115,11 +1115,11 @@ void MainWindow::runExecutable(const QString &exeName,const QString &filename) showMinimized(); } updateAppTitle(); + QString params; if (pSettings->executor().useParams()) { - mCompilerManager->run(exeName,pSettings->executor().params(),QFileInfo(exeName).absolutePath()); - } else { - mCompilerManager->run(exeName,"",QFileInfo(exeName).absolutePath()); + params = pSettings->executor().params(); } + mCompilerManager->run(exeName,params,QFileInfo(exeName).absolutePath()); } void MainWindow::runExecutable() diff --git a/RedPandaIDE/settingsdialog/settingsdialog.cpp b/RedPandaIDE/settingsdialog/settingsdialog.cpp index c0f2be56..fbfc64d5 100644 --- a/RedPandaIDE/settingsdialog/settingsdialog.cpp +++ b/RedPandaIDE/settingsdialog/settingsdialog.cpp @@ -250,6 +250,7 @@ bool SettingsDialog::setCurrentWidget(const QString &widgetName, const QString & for (int i=0;irowCount();i++) { QStandardItem* pWidgetItem = pGroupItem->child(i); if (pWidgetItem->text() == widgetName) { + ui->widgetsView->setCurrentIndex(pWidgetItem->index()); on_widgetsView_clicked(pWidgetItem->index()); return true; } diff --git a/RedPandaIDE/utils.cpp b/RedPandaIDE/utils.cpp index 5518f23c..d245221c 100644 --- a/RedPandaIDE/utils.cpp +++ b/RedPandaIDE/utils.cpp @@ -835,3 +835,12 @@ bool removeFile(const QString &filename) QFile file(filename); return file.remove(); } + +QByteArray ReadFileToByteArray(const QString &fileName) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly)) { + return file.readAll(); + } + return QByteArray(); +} diff --git a/RedPandaIDE/utils.h b/RedPandaIDE/utils.h index 9f4fbd86..ffef72e9 100644 --- a/RedPandaIDE/utils.h +++ b/RedPandaIDE/utils.h @@ -154,6 +154,7 @@ QString parseMacros(const QString& s); QStringList ReadFileToLines(const QString& fileName, QTextCodec* codec); QStringList ReadFileToLines(const QString& fileName); +QByteArray ReadFileToByteArray(const QString& fileName); void ReadFileToLines(const QString& fileName, QTextCodec* codec, LineProcessFunc lineFunc); void StringsToFile(const QStringList& list, const QString& fileName); void StringToFile(const QString& str, const QString& fileName); diff --git a/tools/ConsolePauser/.gitignore b/tools/ConsolePauser/.gitignore new file mode 100644 index 00000000..b5be7eb0 --- /dev/null +++ b/tools/ConsolePauser/.gitignore @@ -0,0 +1,4 @@ +/ConsolePauser.layout +/Makefile.win +/ConsolePauser.exe +/main.o diff --git a/tools/ConsolePauser/ConsolePauser.dev b/tools/ConsolePauser/ConsolePauser.dev new file mode 100644 index 00000000..708a05a0 --- /dev/null +++ b/tools/ConsolePauser/ConsolePauser.dev @@ -0,0 +1,73 @@ +[Project] +FileName = ConsolePauser.dev +Name = ConsolePauser +Type = 1 +Ver = 3 +ObjFiles = +Includes = +Libs = +PrivateResource = +ResourceIncludes = +MakeIncludes = +Compiler = +CppCompiler = +Linker = +IsCpp = 1 +Icon = +ExeOutput = +ObjectOutput = +LogOutput = +LogOutputEnabled = 0 +OverrideOutput = 0 +OverrideOutputName = ConsolePauser.exe +HostApplication = +Folders = +CommandLine = +UseCustomMakefile = 0 +CustomMakefile = +IncludeVersionInfo = 0 +SupportXPThemes = 0 +CompilerSet = 0 +CompilerSettings = 000000a000110000000010000 +UnitCount = 1 +UsePrecompiledHeader = 0 +PrecompiledHeader = +UseUTF8 = 0 +StaticLink = 1 +AddCharset = 1 +Encoding = AUTO + + +[VersionInfo] +Major = 1 +Minor = 0 +Release = 0 +Build = 0 +LanguageID = 1033 +CharsetID = 1252 +CompanyName = +FileVersion = 1.0.0.0 +FileDescription = Developed using the Dev-C++ IDE +InternalName = +LegalCopyright = +LegalTrademarks = +OriginalFilename = +ProductName = +ProductVersion = 1.0.0.0 +AutoIncBuildNr = 0 +SyncProduct = 1 + + +[Unit1] +FileName = main.cpp +CompileCpp = 1 +Folder = +Compile = 1 +Link = 1 +Priority = 1000 +OverrideBuildCmd = 0 +BuildCmd = +UseUTF8 = 1 +DetectEncoding = 1 +Encoding = 0 +FileEncoding = AUTO diff --git a/tools/ConsolePauser/main.cpp b/tools/ConsolePauser/main.cpp new file mode 100644 index 00000000..0043ad76 --- /dev/null +++ b/tools/ConsolePauser/main.cpp @@ -0,0 +1,217 @@ +// Execute & Pause +// Runs a program, then keeps the console window open after it finishes + +#include +using std::string; +#include +#include + +#define MAX_COMMAND_LENGTH 32768 +#define MAX_ERROR_LENGTH 2048 + +HANDLE hJob; + +LONGLONG GetClockTick() { + LARGE_INTEGER dummy; + QueryPerformanceCounter(&dummy); + return dummy.QuadPart; +} + +LONGLONG GetClockFrequency() { + LARGE_INTEGER dummy; + QueryPerformanceFrequency(&dummy); + return dummy.QuadPart; +} + + +string GetErrorMessage() { + string result(MAX_ERROR_LENGTH,0); + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),&result[0],result.size(),NULL); + + // Clear newlines at end of string + for(int i = result.length()-1;i >= 0;i--) { + if(isspace(result[i])) { + result[i] = 0; + } else { + break; + } + } + return result; +} + +void PauseExit(int exitcode, bool reInp) { + HANDLE hInp=NULL; + if (reInp) { + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + HANDLE hInp = CreateFile("CONIN$", GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ , &sa, OPEN_EXISTING, /*FILE_ATTRIBUTE_NORMAL*/0, NULL); + //si.hStdInput = hInp; + SetStdHandle(STD_INPUT_HANDLE,hInp); + } + //system("pause"); + + STARTUPINFO si; + PROCESS_INFORMATION pi; + memset(&si,0,sizeof(si)); + si.cb = sizeof(si); + memset(&pi,0,sizeof(pi)); + + DWORD dwCreationFlags = CREATE_BREAKAWAY_FROM_JOB; + + + if(!CreateProcess(NULL, (LPSTR)"cmd /c \"pause\"", NULL, NULL, true, dwCreationFlags, NULL, NULL, &si, &pi)) { + printf("\n--------------------------------"); + printf("\nFailed to execute 'pause' "); + printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str()); + system("pause"); + exit(exitcode); + } + WINBOOL bSuccess = AssignProcessToJobObject( hJob, pi.hProcess ); + if ( bSuccess == FALSE ) { + printf( "AssignProcessToJobObject failed: error %d\n", GetLastError() ); + system("pause"); + exit(exitcode); + } + + WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish + if (reInp) { + CloseHandle(hInp); + } + CloseHandle( hJob ); + exit(exitcode); +} + +string GetCommand(int argc,char** argv,bool &reInp) { + string result; + reInp = (strcmp(argv[1],"0")!=0) ; + for(int i = 2;i < argc;i++) { +/* + // Quote the first argument in case the path name contains spaces +// if(i == 1) { +// result += string("\"") + string(argv[i]) + string("\""); +// } else { + // Quote the first argument in case the path name contains spaces +// result += string(argv[i]); +// } +*/ + // Quote the first argument in case the path name contains spaces + result += string("\"") + string(argv[i]) + string("\""); + + // Add a space except for the last argument + if(i != (argc-1)) { + result += string(" "); + } + } + + if(result.length() > MAX_COMMAND_LENGTH) { + printf("\n--------------------------------"); + printf("\nError: Length of command line string is over %d characters\n",MAX_COMMAND_LENGTH); + PauseExit(EXIT_FAILURE,reInp); + } + + return result; +} + +DWORD ExecuteCommand(string& command,bool reInp) { + STARTUPINFO si; + PROCESS_INFORMATION pi; + + memset(&si,0,sizeof(si)); + si.cb = sizeof(si); + memset(&pi,0,sizeof(pi)); + + DWORD dwCreationFlags = CREATE_BREAKAWAY_FROM_JOB; + + + if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, true, dwCreationFlags, NULL, NULL, &si, &pi)) { + printf("\n--------------------------------"); + printf("\nFailed to execute \"%s\":",command.c_str()); + printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str()); + PauseExit(EXIT_FAILURE,reInp); + } + WINBOOL bSuccess = AssignProcessToJobObject( hJob, pi.hProcess ); + if ( bSuccess == FALSE ) { + printf( "AssignProcessToJobObject failed: error %d\n", GetLastError() ); + return 0; + } + + WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish + + + DWORD result = 0; + GetExitCodeProcess(pi.hProcess, &result); + return result; +} + +int main(int argc, char** argv) { + + // First make sure we aren't going to read nonexistent arrays + if(argc < 3) { + printf("\n--------------------------------"); + printf("\nUsage: ConsolePauser.exe <0|1> \n"); + printf("\n 1 means the STDIN is redirected by Dev-CPP;0 means not\n"); + PauseExit(EXIT_SUCCESS,false); + } + + // Make us look like the paused program + SetConsoleTitle(argv[2]); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + + hJob= CreateJobObject( &sa, NULL ); + + if ( hJob == NULL ) { + printf( "CreateJobObject failed: error %d\n", GetLastError() ); + return 0; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = { 0 }; + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + WINBOOL bSuccess = SetInformationJobObject( hJob, JobObjectExtendedLimitInformation, &info, sizeof( info ) ); + if ( bSuccess == FALSE ) { + printf( "SetInformationJobObject failed: error %d\n", GetLastError() ); + return 0; + } + + bool reInp; + // Then build the to-run application command + string command = GetCommand(argc,argv,reInp); + HANDLE hOutput = NULL; + if (reInp) { + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + hOutput = CreateFile("CONOUT$", GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_WRITE , &sa, OPEN_EXISTING, /*FILE_ATTRIBUTE_NORMAL*/0, NULL); + SetStdHandle(STD_OUTPUT_HANDLE, hOutput); + SetStdHandle(STD_ERROR_HANDLE, hOutput); + freopen("CONOUT$","w+",stdout); + freopen("CONOUT$","w+",stderr); + } + + // Save starting timestamp + LONGLONG starttime = GetClockTick(); + + // Then execute said command + DWORD returnvalue = ExecuteCommand(command,reInp); + + // Get ending timestamp + LONGLONG endtime = GetClockTick(); + double seconds = (endtime - starttime) / (double)GetClockFrequency(); + + // Done? Print return value of executed program + printf("\n--------------------------------"); + printf("\nProcess exited after %.4g seconds with return value %lu\n",seconds,returnvalue); + PauseExit(returnvalue,reInp); +}