diff --git a/common/include/Utilities/Console.h b/common/include/Utilities/Console.h index 3cec1595c0..2f27446622 100644 --- a/common/include/Utilities/Console.h +++ b/common/include/Utilities/Console.h @@ -238,6 +238,9 @@ public: extern IConsoleWriter Console; +#ifdef __linux__ +extern void Console_SetStdout(FILE *fp); +#endif extern void Console_SetActiveHandler( const IConsoleWriter& writer, FILE* flushfp=NULL ); extern const wxString& ConsoleBuffer_Get(); extern void ConsoleBuffer_Clear(); diff --git a/common/src/Utilities/Console.cpp b/common/src/Utilities/Console.cpp index d1a9dffb35..97f1623469 100644 --- a/common/src/Utilities/Console.cpp +++ b/common/src/Utilities/Console.cpp @@ -32,6 +32,15 @@ static DeclareTls(ConsoleColors) conlog_Color( DefaultConsoleColor ); static wxString m_buffer; // used by ConsoleBuffer static Mutex m_bufferlock; // used by ConsoleBuffer +#ifdef __linux__ +static FILE *stdout_fp = stdout; + +void Console_SetStdout(FILE *fp) +{ + stdout_fp = fp; +} +#endif + // This function re-assigns the console log writer(s) to the specified target. It makes sure // to flush any contents from the buffered console log (which typically accumulates due to // log suspension during log file/window re-init operations) into the new log. @@ -71,11 +80,12 @@ void MSW_OutputDebugString( const wxString& text ) static bool hasDebugger = wxIsDebuggerRunning(); if( hasDebugger ) OutputDebugString( text ); #else - fputs(text.utf8_str(), stdout); - fflush(stdout); + fputs(text.utf8_str(), stdout_fp); + fflush(stdout_fp); #endif } + // -------------------------------------------------------------------------------------- // ConsoleNull // -------------------------------------------------------------------------------------- @@ -165,17 +175,17 @@ static void __concall ConsoleStdout_Newline() static void __concall ConsoleStdout_DoSetColor( ConsoleColors color ) { #ifdef __linux__ - fprintf(stdout, "\033[0m%s", GetLinuxConsoleColor(color)); - fflush(stdout); + fprintf(stdout_fp, "\033[0m%s", GetLinuxConsoleColor(color)); + fflush(stdout_fp); #endif } static void __concall ConsoleStdout_SetTitle( const wxString& title ) { #ifdef __linux__ - fputs("\033]0;", stdout); - fputs(title.utf8_str(), stdout); - fputs("\007", stdout); + fputs("\033]0;", stdout_fp); + fputs(title.utf8_str(), stdout_fp); + fputs("\007", stdout_fp); #endif } diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 119b1fcf78..3ed249db33 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -394,6 +394,7 @@ set(pcsx2IPUHeaders # Linux sources set(pcsx2LinuxSources + Linux/LnxConsolePipe.cpp Linux/LnxKeyCodes.cpp Linux/LnxFlatFileReader.cpp ) diff --git a/pcsx2/Linux/LnxConsolePipe.cpp b/pcsx2/Linux/LnxConsolePipe.cpp new file mode 100644 index 0000000000..30b8abd7a4 --- /dev/null +++ b/pcsx2/Linux/LnxConsolePipe.cpp @@ -0,0 +1,191 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2015 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "App.h" +#include "ConsoleLogger.h" +#include + +using namespace Threading; + +// Reads data sent to stdout/stderr and sends it to the PCSX2 console. +class LinuxPipeThread : public pxThread +{ + typedef pxThread _parent; + +protected: + const int& m_read_fd; + const ConsoleColors m_color; + + void ExecuteTaskInThread(); +public: + LinuxPipeThread(const int& read_fd, ConsoleColors color); + virtual ~LinuxPipeThread() noexcept; +}; + +LinuxPipeThread::LinuxPipeThread(const int& read_fd, ConsoleColors color) + : pxThread(color == Color_Red ? L"Redirect_Stderr" : L"Redirect_Stdout") + , m_read_fd(read_fd) + , m_color(color) +{ +} + +LinuxPipeThread::~LinuxPipeThread() noexcept +{ + _parent::Cancel(); +} + +void LinuxPipeThread::ExecuteTaskInThread() +{ + char buffer[2049]; + while (ssize_t bytes_read = read(m_read_fd, buffer, sizeof(buffer) - 1)) { + TestCancel(); + + if (bytes_read == -1) { + if (errno == EINTR) { + continue; + } + else { + // Should never happen. + Console.Error(wxString::Format(L"Redirect %s failed: read: %s", + m_color == Color_Red ? L"stderr" : L"stdout", strerror(errno))); + break; + } + } + + buffer[bytes_read] = 0; + + { + ConsoleColorScope cs(m_color); + Console.WriteRaw(fromUTF8(buffer)); + } + + TestCancel(); + } +} + +// Redirects data sent to stdout/stderr into a pipe, and sets up a thread to +// forward data to the console. +class LinuxPipeRedirection : public PipeRedirectionBase +{ + DeclareNoncopyableObject(LinuxPipeRedirection); + +protected: + FILE* m_stdstream; + FILE* m_fp; + int m_pipe_fd[2]; + LinuxPipeThread m_thread; + + void Cleanup() noexcept; +public: + LinuxPipeRedirection(FILE* stdstream); + virtual ~LinuxPipeRedirection() noexcept; +}; + +LinuxPipeRedirection::LinuxPipeRedirection(FILE* stdstream) + : m_stdstream(stdstream) + , m_fp(nullptr) + , m_pipe_fd{-1, -1} + , m_thread(m_pipe_fd[0], stdstream == stderr ? Color_Red : Color_Black) +{ + pxAssert((stdstream == stderr) || (stdstream == stdout)); + + const wxChar* stream_name = stdstream == stderr? L"stderr" : L"stdout"; + + try { + int stdstream_fd = fileno(stdstream); + + // Save the original stdout/stderr file descriptor... + int dup_fd = dup(stdstream_fd); + if (dup_fd == -1) + throw Exception::RuntimeError().SetDiagMsg(wxString::Format( + L"Redirect %s failed: dup: %s", stream_name, strerror(errno))); + m_fp = fdopen(dup_fd, "w"); + if (m_fp == nullptr) + throw Exception::RuntimeError().SetDiagMsg(wxString::Format( + L"Redirect %s failed: fdopen: %s", stream_name, strerror(errno))); + + // and now redirect stdout/stderr. + if (pipe(m_pipe_fd) == -1) + throw Exception::RuntimeError().SetDiagMsg(wxString::Format( + L"Redirect %s failed: pipe: %s", stream_name, strerror(errno))); + if (dup2(m_pipe_fd[1], stdstream_fd) != stdstream_fd) + throw Exception::RuntimeError().SetDiagMsg(wxString::Format( + L"Redirect %s failed: dup2: %s", stream_name, strerror(errno))); + close(m_pipe_fd[1]); + m_pipe_fd[1] = stdstream_fd; + + // And send the final console output goes to the original stdout, + // otherwise we'll have an infinite data loop. + if (stdstream == stdout) + Console_SetStdout(m_fp); + + m_thread.Start(); + } catch (Exception::BaseThreadError& ex) { + // thread object will become invalid because of scoping after we leave + // the constructor, so re-pack a new exception: + Cleanup(); + throw Exception::RuntimeError().SetDiagMsg(ex.FormatDiagnosticMessage()); + } catch (...) { + Cleanup(); + throw; + } +} + +LinuxPipeRedirection::~LinuxPipeRedirection() noexcept +{ + Cleanup(); +} + +void LinuxPipeRedirection::Cleanup() noexcept +{ + // Restore stdout/stderr file descriptor - mostly useful if the thread + // fails to start, but then you have bigger issues to worry about. + if (m_pipe_fd[1] != -1) { + if (m_pipe_fd[1] == fileno(m_stdstream)) { + // FIXME: Use lock for better termination. + // The redirect thread is most likely waiting at read(). Send a + // newline so it returns and the thread begins to terminate. + fflush(m_stdstream); + m_thread.Cancel(); + fputc('\n', m_stdstream); + fflush(m_stdstream); + dup2(fileno(m_fp), fileno(m_stdstream)); + } else { + close(m_pipe_fd[1]); + } + } + + if (m_fp != nullptr) { + if (m_stdstream == stdout) + Console_SetStdout(stdout); + fclose(m_fp); + } + + if (m_pipe_fd[0] != -1) + close(m_pipe_fd[0]); +} + +PipeRedirectionBase* NewPipeRedir(FILE* stdstream) +{ + try { + return new LinuxPipeRedirection(stdstream); + } catch (Exception::RuntimeError& ex) { + Console.Error(ex.FormatDiagnosticMessage()); + } + + return nullptr; +} diff --git a/pcsx2/Linux/LnxKeyCodes.cpp b/pcsx2/Linux/LnxKeyCodes.cpp index 4c01bc6d36..55f513ce81 100644 --- a/pcsx2/Linux/LnxKeyCodes.cpp +++ b/pcsx2/Linux/LnxKeyCodes.cpp @@ -294,13 +294,3 @@ int TranslateGDKtoWXK( u32 keysym ) return key_code; } - -// NewPipeRedir .. Homeless function for now .. This is as good a spot as any. -// Eventually we might be so fancy as to have a linux console pipe to our own console -// window, same as the Win32 one. Not sure how doable it is, and it's not as urgent -// anyway since Linux has better generic console support and commandline piping. -// -PipeRedirectionBase* NewPipeRedir( FILE* stdstream ) -{ - return NULL; -} diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj index 065f826e7e..f878f6fd66 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj @@ -438,6 +438,9 @@ + + true + diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters index 32363794bf..672aa9f2d1 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters @@ -874,6 +874,9 @@ AppHost\Win32 + + AppHost\Linux +