From 5f5556ee69bcf43e8eddaee037a26289edd3a1f9 Mon Sep 17 00:00:00 2001 From: Jonathan Li Date: Sat, 31 Oct 2015 10:10:41 +0000 Subject: [PATCH] gui:linux: Show plugin stdout/stderr in pcsx2 console Behaviour is now similar to Windows, except we can also show messages in the terminal as well. --- pcsx2/CMakeLists.txt | 1 + pcsx2/Linux/LnxConsolePipe.cpp | 191 ++++++++++++++++++ pcsx2/Linux/LnxKeyCodes.cpp | 10 - pcsx2/windows/VCprojects/pcsx2.vcxproj | 3 + .../windows/VCprojects/pcsx2.vcxproj.filters | 3 + 5 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 pcsx2/Linux/LnxConsolePipe.cpp 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 +