From 784ee7d511ddec88c2a53c1b50c85850eb341dc6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 23 May 2008 23:28:13 +0000 Subject: [PATCH] improve wxMessageOutputBest console output under Windows (closes 9146) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@53730 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/msgout.h | 34 +++-- include/wx/msw/apptbase.h | 17 +++ include/wx/msw/apptrait.h | 10 +- src/common/appcmn.cpp | 5 +- src/common/msgout.cpp | 63 ++++----- src/msw/app.cpp | 273 ++++++++++++++++++++++++++++++++++++++ src/msw/basemsw.cpp | 4 + 7 files changed, 355 insertions(+), 51 deletions(-) diff --git a/include/wx/msgout.h b/include/wx/msgout.h index b670f2b653..4f85b3581c 100644 --- a/include/wx/msgout.h +++ b/include/wx/msgout.h @@ -101,31 +101,44 @@ private: #endif // ---------------------------------------------------------------------------- -// implementation showing the message to the user in "best" possible way: uses -// native message box if available (currently only under Windows) and stderr -// otherwise; unlike wxMessageOutputMessageBox this class is always safe to use +// implementation which sends output to stderr // ---------------------------------------------------------------------------- -class WXDLLIMPEXP_BASE wxMessageOutputBest : public wxMessageOutput +class WXDLLIMPEXP_BASE wxMessageOutputStderr : public wxMessageOutput { public: - wxMessageOutputBest() { } + wxMessageOutputStderr() { } protected: virtual void Output(const wxString& str); + + // return the string with "\n" appended if it doesn't already terminate + // with it (in which case it's returned unchanged) + wxString AppendLineFeedIfNeeded(const wxString& str); }; // ---------------------------------------------------------------------------- -// implementation which sends output to stderr +// implementation showing the message to the user in "best" possible way: +// uses stderr or message box if available according to the flag given to ctor. // ---------------------------------------------------------------------------- -class WXDLLIMPEXP_BASE wxMessageOutputStderr : public wxMessageOutput +enum wxMessageOutputFlags +{ + wxMSGOUT_PREFER_STDERR = 0, // use stderr if available (this is the default) + wxMSGOUT_PREFER_MSGBOX = 1 // always use message box if available +}; + +class WXDLLIMPEXP_BASE wxMessageOutputBest : public wxMessageOutputStderr { public: - wxMessageOutputStderr() { } + wxMessageOutputBest(wxMessageOutputFlags flags = wxMSGOUT_PREFER_STDERR) + : m_flags(flags) { } protected: virtual void Output(const wxString& str); + +private: + wxMessageOutputFlags m_flags; }; // ---------------------------------------------------------------------------- @@ -149,7 +162,7 @@ protected: // implementation using the native way of outputting debug messages // ---------------------------------------------------------------------------- -class WXDLLIMPEXP_BASE wxMessageOutputDebug : public wxMessageOutput +class WXDLLIMPEXP_BASE wxMessageOutputDebug : public wxMessageOutputStderr { public: wxMessageOutputDebug() { } @@ -171,5 +184,4 @@ protected: virtual void Output(const wxString& str); }; -#endif - // _WX_MSGOUT_H_ +#endif // _WX_MSGOUT_H_ diff --git a/include/wx/msw/apptbase.h b/include/wx/msw/apptbase.h index 34101ac4ba..851b28c856 100644 --- a/include/wx/msw/apptbase.h +++ b/include/wx/msw/apptbase.h @@ -65,6 +65,23 @@ public: virtual GSocketManager *GetSocketManager() { return ms_manager; } #endif // wxUSE_SOCKETS + +#ifndef __WXWINCE__ + // console helpers + // --------------- + + // this method can be overridden by a derived class to always return true + // or false to force [not] using the console for output to stderr + // + // by default console applications always return true from here while the + // GUI ones only return true if they're being run from console and there is + // no other activity happening in this console + virtual bool CanUseStderr() = 0; + + // write text to the console, return true if ok or false on error + virtual bool WriteToStderr(const wxString& text) = 0; +#endif // !__WXWINCE__ + protected: // implementation of WaitForThread() for the console applications which is // also used by the GUI code if it doesn't [yet|already} dispatch events diff --git a/include/wx/msw/apptrait.h b/include/wx/msw/apptrait.h index aca686e5fa..0c8f8ea2bf 100644 --- a/include/wx/msw/apptrait.h +++ b/include/wx/msw/apptrait.h @@ -28,6 +28,10 @@ public: #endif virtual bool DoMessageFromThreadWait(); virtual WXDWORD WaitForThread(WXHANDLE hThread); +#ifndef __WXWINCE__ + virtual bool CanUseStderr() { return true; } + virtual bool WriteToStderr(const wxString& text); +#endif // !__WXWINCE__ }; #if wxUSE_GUI @@ -45,9 +49,13 @@ public: virtual bool DoMessageFromThreadWait(); virtual wxPortId GetToolkitVersion(int *majVer = NULL, int *minVer = NULL) const; virtual WXDWORD WaitForThread(WXHANDLE hThread); + +#ifndef __WXWINCE__ + virtual bool CanUseStderr(); + virtual bool WriteToStderr(const wxString& text); +#endif // !__WXWINCE__ }; #endif // wxUSE_GUI #endif // _WX_MSW_APPTRAIT_H_ - diff --git a/src/common/appcmn.cpp b/src/common/appcmn.cpp index 15f409dbd6..54e9204d59 100644 --- a/src/common/appcmn.cpp +++ b/src/common/appcmn.cpp @@ -437,7 +437,8 @@ wxMessageOutput *wxGUIAppTraitsBase::CreateMessageOutput() // is (according to common practice): // - console apps: to stderr (on any platform) // - GUI apps: stderr on Unix platforms (!) - // message box under Windows and others + // stderr if available and message box otherwise on others + // (currently stderr only Windows if app running from console) #ifdef __UNIX__ return new wxMessageOutputStderr; #else // !__UNIX__ @@ -445,7 +446,7 @@ wxMessageOutput *wxGUIAppTraitsBase::CreateMessageOutput() #ifdef __WXMOTIF__ return new wxMessageOutputLog; #elif wxUSE_MSGDLG - return new wxMessageOutputMessageBox; + return new wxMessageOutputBest(wxMSGOUT_PREFER_STDERR); #else return new wxMessageOutputStderr; #endif diff --git a/src/common/msgout.cpp b/src/common/msgout.cpp index 9fabb4a987..e6a471d9b3 100644 --- a/src/common/msgout.cpp +++ b/src/common/msgout.cpp @@ -105,48 +105,42 @@ void wxMessageOutput::DoPrintfUtf8(const char *format, ...) // wxMessageOutputBest // ---------------------------------------------------------------------------- -#ifdef __WINDOWS__ - -// check if we're running in a console under Windows -static inline bool IsInConsole() -{ -#ifdef __WXWINCE__ - return false; -#else // !__WXWINCE__ - HANDLE hStdErr = ::GetStdHandle(STD_ERROR_HANDLE); - return hStdErr && hStdErr != INVALID_HANDLE_VALUE; -#endif // __WXWINCE__/!__WXWINCE__ -} - -#endif // __WINDOWS__ - void wxMessageOutputBest::Output(const wxString& str) { #ifdef __WINDOWS__ - if ( !IsInConsole() ) + // decide whether to use console output or not + wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits() : NULL; + const bool hasStderr = traits ? traits->CanUseStderr() : false; + + if ( !(m_flags & wxMSGOUT_PREFER_MSGBOX) ) { - ::MessageBox(NULL, str.wx_str(), _T("wxWidgets"), - MB_ICONINFORMATION | MB_OK); + if ( hasStderr && traits->WriteToStderr(AppendLineFeedIfNeeded(str)) ) + return; } - else -#endif // __WINDOWS__/!__WINDOWS__ - { - const wxWX2MBbuf buf = str.mb_str(); - if ( buf ) - fprintf(stderr, "%s", (const char*) buf); - else // print at least something - fprintf(stderr, "%s", (const char*) str.ToAscii()); - } + ::MessageBox(NULL, str.wx_str(), NULL, MB_ICONINFORMATION | MB_OK); +#else // !__WINDOWS__ + // TODO: use the native message box for the other ports too + wxMessageOutputStderr::Output(str); +#endif // __WINDOWS__/!__WINDOWS__ } // ---------------------------------------------------------------------------- // wxMessageOutputStderr // ---------------------------------------------------------------------------- +wxString wxMessageOutputStderr::AppendLineFeedIfNeeded(const wxString& str) +{ + wxString strLF(str); + if ( strLF.empty() || *strLF.rbegin() != '\n' ) + strLF += '\n'; + + return strLF; +} + void wxMessageOutputStderr::Output(const wxString& str) { - const wxWX2MBbuf buf = str.mb_str(); + const wxWX2MBbuf buf = AppendLineFeedIfNeeded(str).mb_str(); if ( buf ) fprintf(stderr, "%s", (const char*) buf); @@ -160,17 +154,14 @@ void wxMessageOutputStderr::Output(const wxString& str) void wxMessageOutputDebug::Output(const wxString& str) { - wxString out(str); - #if defined(__WXMSW__) && !defined(__WXMICROWIN__) + wxString out(AppendLineFeedIfNeeded(str)); out.Replace(wxT("\t"), wxT(" ")); out.Replace(wxT("\n"), wxT("\r\n")); ::OutputDebugString(out.wx_str()); #else - wxFputs( out , stderr ) ; - if ( out.Right(1) != wxT("\n") ) - wxFputs( wxT("\n") , stderr ) ; - fflush( stderr ) ; + // TODO: use native debug output function for the other ports too + wxMessageOutputStderr::Output(str); #endif // platform } @@ -204,9 +195,7 @@ void wxMessageOutputMessageBox::Output(const wxString& str) out.Replace(wxT("\t"), wxT(" ")); #endif - wxString title; - if ( wxTheApp ) - title.Printf(_("%s message"), wxTheApp->GetAppDisplayName().c_str()); + wxString title = wxTheApp ? wxTheApp->GetAppDisplayName() : wxT("wxWidgets"); ::wxMessageBox(out, title); } diff --git a/src/msw/app.cpp b/src/msw/app.cpp index 5012573d09..32779c348d 100644 --- a/src/msw/app.cpp +++ b/src/msw/app.cpp @@ -282,6 +282,279 @@ wxEventLoopBase* wxGUIAppTraits::CreateEventLoop() return new wxEventLoop; } +// --------------------------------------------------------------------------- +// Stuff for using console from the GUI applications +// --------------------------------------------------------------------------- + +#ifndef __WXWINCE__ + +#include + +namespace +{ + +/* + Helper class to manipulate console from a GUI app. + + Notice that console output is available in the GUI app only if: + - AttachConsole() returns TRUE (which means it never works under pre-XP) + - we have a valid STD_ERROR_HANDLE + - command history hasn't been changed since our startup + + To check if all these conditions are verified, you need to simple call + IsOkToUse(). It will check the first two conditions above the first time it + is called (and if this fails, the subsequent calls will return immediately) + and also recheck the last one every time it is called. + */ +class wxConsoleStderr +{ +public: + // default ctor does nothing, call Init() before using this class + wxConsoleStderr() + { + m_hStderr = INVALID_HANDLE_VALUE; + m_historyLen = + m_dataLen = + m_dataLine = 0; + + m_ok = -1; + } + + ~wxConsoleStderr() + { + if ( m_hStderr != INVALID_HANDLE_VALUE ) + { + if ( !::FreeConsole() ) + { + wxLogLastError(_T("FreeConsole")); + } + } + } + + // return true if we were successfully initialized and there had been no + // console activity which would interfere with our output since then + bool IsOkToUse() const + { + if ( m_ok == -1 ) + { + wxConsoleStderr * const self = wx_const_cast(wxConsoleStderr *, this); + self->m_ok = self->DoInit(); + + // no need to call IsHistoryUnchanged() as we just initialized + // m_history anyhow + return m_ok == 1; + } + + return m_ok && IsHistoryUnchanged(); + } + + + // output the provided text on the console, return true if ok + bool Write(const wxString& text); + +private: + // called by Init() once only to do the real initialization + bool DoInit(); + + // retrieve the command line history into the provided buffer and return + // its length + int GetCommandHistory(wxWxCharBuffer& buf) const; + + // check if the console history has changed + bool IsHistoryUnchanged() const; + + int m_ok; // initially -1, set to true or false by Init() + + wxDynamicLibrary m_dllKernel32; + + HANDLE m_hStderr; // console handle, if it's valid we must call + // FreeConsole() (even if m_ok != 1) + + wxWxCharBuffer m_history; // command history on startup + int m_historyLen; // length command history buffer + + wxCharBuffer m_data; // data between empty line and cursor position + int m_dataLen; // length data buffer + int m_dataLine; // line offset + + typedef DWORD (WINAPI *GetConsoleCommandHistory_t)(LPTSTR sCommands, + DWORD nBufferLength, + LPCTSTR sExeName); + typedef DWORD (WINAPI *GetConsoleCommandHistoryLength_t)(LPCTSTR sExeName); + + GetConsoleCommandHistory_t m_pfnGetConsoleCommandHistory; + GetConsoleCommandHistoryLength_t m_pfnGetConsoleCommandHistoryLength; + + DECLARE_NO_COPY_CLASS(wxConsoleStderr) +}; + +bool wxConsoleStderr::DoInit() +{ + HANDLE hStderr = ::GetStdHandle(STD_ERROR_HANDLE); + + if ( hStderr == INVALID_HANDLE_VALUE || !hStderr ) + return false; + + if ( !m_dllKernel32.Load(_T("kernel32.dll")) ) + return false; + + typedef BOOL (WINAPI *AttachConsole_t)(DWORD dwProcessId); + AttachConsole_t wxDL_INIT_FUNC(pfn, AttachConsole, m_dllKernel32); + + if ( !pfnAttachConsole || !pfnAttachConsole(ATTACH_PARENT_PROCESS) ) + return false; + + // console attached, set m_hStderr now to ensure that we free it in the + // dtor + m_hStderr = hStderr; + + wxDL_INIT_FUNC_AW(m_pfn, GetConsoleCommandHistory, m_dllKernel32); + if ( !m_pfnGetConsoleCommandHistory ) + return false; + + wxDL_INIT_FUNC_AW(m_pfn, GetConsoleCommandHistoryLength, m_dllKernel32); + if ( !m_pfnGetConsoleCommandHistoryLength ) + return false; + + // remember the current command history to be able to compare with it later + // in IsHistoryUnchanged() + m_historyLen = GetCommandHistory(m_history); + if ( !m_history ) + return false; + + + // now find the first blank line above the current position + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if ( !::GetConsoleScreenBufferInfo(m_hStderr, &csbi) ) + { + wxLogLastError(_T("GetConsoleScreenBufferInfo")); + return false; + } + + COORD pos; + pos.X = 0; + pos.Y = csbi.dwCursorPosition.Y + 1; + + // we decide that a line is empty if first 4 characters are spaces + DWORD ret; + char buf[4]; + do + { + pos.Y--; + if ( !::ReadConsoleOutputCharacterA(m_hStderr, buf, WXSIZEOF(buf), + pos, &ret) ) + { + wxLogLastError(_T("ReadConsoleOutputCharacterA")); + return false; + } + } while ( wxStrncmp(" ", buf, WXSIZEOF(buf)) != 0 ); + + // calculate line offset and length of data + m_dataLine = csbi.dwCursorPosition.Y - pos.Y; + m_dataLen = m_dataLine*csbi.dwMaximumWindowSize.X + csbi.dwCursorPosition.X; + + if ( m_dataLen > 0 ) + { + m_data.extend(m_dataLen); + if ( !::ReadConsoleOutputCharacterA(m_hStderr, m_data.data(), m_dataLen, + pos, &ret) ) + { + wxLogLastError(_T("ReadConsoleOutputCharacterA")); + return false; + } + } + + return true; +} + +int wxConsoleStderr::GetCommandHistory(wxWxCharBuffer& buf) const +{ + // these functions are internal and may only be called by cmd.exe + static const wxChar *CMD_EXE = _T("cmd.exe"); + + const int len = m_pfnGetConsoleCommandHistoryLength(CMD_EXE); + if ( len ) + { + buf.extend(len); + const int len2 = m_pfnGetConsoleCommandHistory(buf.data(), len, CMD_EXE); + wxASSERT_MSG( len2 == len, _T("failed getting history?") ); + } + + return len; +} + +bool wxConsoleStderr::IsHistoryUnchanged() const +{ + wxASSERT_MSG( m_ok == 1, _T("shouldn't be called if not initialized") ); + + // get (possibly changed) command history + wxWxCharBuffer history; + const int historyLen = GetCommandHistory(history); + + // and compare it with the original one + return historyLen == m_historyLen && history && + memcmp(m_history, history, historyLen) == 0; +} + +bool wxConsoleStderr::Write(const wxString& text) +{ + wxASSERT_MSG( m_hStderr != INVALID_HANDLE_VALUE, + _T("should only be called if Init() returned true") ); + + // get current position + CONSOLE_SCREEN_BUFFER_INFO csbi; + if ( !::GetConsoleScreenBufferInfo(m_hStderr, &csbi) ) + { + wxLogLastError(_T("GetConsoleScreenBufferInfo")); + return false; + } + + // and calculate new position (where is empty line) + csbi.dwCursorPosition.X = 0; + csbi.dwCursorPosition.Y -= m_dataLine; + + if ( !::SetConsoleCursorPosition(m_hStderr, csbi.dwCursorPosition) ) + { + wxLogLastError(_T("SetConsoleCursorPosition")); + return false; + } + + DWORD ret; + if ( !::FillConsoleOutputCharacter(m_hStderr, _T(' '), m_dataLen, + csbi.dwCursorPosition, &ret) ) + { + wxLogLastError(_T("FillConsoleOutputCharacter")); + return false; + } + + if ( !::WriteConsole(m_hStderr, text.wx_str(), text.length(), &ret, NULL) ) + { + wxLogLastError(_T("WriteConsole")); + return false; + } + + WriteConsoleA(m_hStderr, m_data, m_dataLen, &ret, 0); + + return true; +} + +wxConsoleStderr s_consoleStderr; + +} // anonymous namespace + +bool wxGUIAppTraits::CanUseStderr() +{ + return s_consoleStderr.IsOkToUse(); +} + +bool wxGUIAppTraits::WriteToStderr(const wxString& text) +{ + return s_consoleStderr.IsOkToUse() && s_consoleStderr.Write(text); +} + +#endif // !__WXWINCE__ + // =========================================================================== // wxApp implementation // =========================================================================== diff --git a/src/msw/basemsw.cpp b/src/msw/basemsw.cpp index 5308abebe3..1763d4d46d 100644 --- a/src/msw/basemsw.cpp +++ b/src/msw/basemsw.cpp @@ -108,3 +108,7 @@ WXDWORD wxConsoleAppTraits::WaitForThread(WXHANDLE hThread) return DoSimpleWaitForThread(hThread); } +bool wxConsoleAppTraits::WriteToStderr(const wxString& text) +{ + return wxFprintf(stderr, "%s", text) != -1; +} -- 2.45.2