From c24cc1df51f300140df6ef94ea1afd00bcbf57ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Heusipp?= Date: Wed, 25 Sep 2024 12:57:41 +0000 Subject: [PATCH] [Ref] openmpt123: Split terminal handling to separate file. [Ref] openmpt123: Split stdio handling to separate file. [Ref] openmpt123: Split exception to separate file to avoid header cycles. git-svn-id: https://source.openmpt.org/svn/openmpt/trunk/OpenMPT@21747 56274372-70c3-4bfc-bfc3-4c3a0b034d27 --- build/autotools/Makefile.am | 3 + build/vs2017winxp/openmpt123.vcxproj | 3 + build/vs2017winxp/openmpt123.vcxproj.filters | 9 + build/vs2017winxpansi/openmpt123.vcxproj | 3 + .../openmpt123.vcxproj.filters | 9 + build/vs2019win7/openmpt123.vcxproj | 3 + build/vs2019win7/openmpt123.vcxproj.filters | 9 + build/vs2022win10/openmpt123.vcxproj | 3 + build/vs2022win10/openmpt123.vcxproj.filters | 9 + build/vs2022win10clang/openmpt123.vcxproj | 3 + .../openmpt123.vcxproj.filters | 9 + build/vs2022win7/openmpt123.vcxproj | 3 + build/vs2022win7/openmpt123.vcxproj.filters | 9 + build/vs2022win8/openmpt123.vcxproj | 3 + build/vs2022win8/openmpt123.vcxproj.filters | 9 + build/vs2022win81/openmpt123.vcxproj | 3 + build/vs2022win81/openmpt123.vcxproj.filters | 9 + openmpt123/openmpt123.cpp | 181 +----- openmpt123/openmpt123.hpp | 373 +------------ openmpt123/openmpt123_exception.hpp | 30 + openmpt123/openmpt123_stdio.hpp | 98 ++++ openmpt123/openmpt123_terminal.hpp | 517 ++++++++++++++++++ 22 files changed, 756 insertions(+), 542 deletions(-) create mode 100644 openmpt123/openmpt123_exception.hpp create mode 100644 openmpt123/openmpt123_stdio.hpp create mode 100644 openmpt123/openmpt123_terminal.hpp diff --git a/build/autotools/Makefile.am b/build/autotools/Makefile.am index 9351c9144f..1c562fe065 100644 --- a/build/autotools/Makefile.am +++ b/build/autotools/Makefile.am @@ -704,6 +704,7 @@ bin_openmpt123_SOURCES += $(MPT_FILES_SRC_MPT) bin_openmpt123_SOURCES += openmpt123/openmpt123_allegro42.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_config.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123.cpp +bin_openmpt123_SOURCES += openmpt123/openmpt123_exception.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_flac.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_mmio.hpp @@ -712,7 +713,9 @@ bin_openmpt123_SOURCES += openmpt123/openmpt123_pulseaudio.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_raw.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_sdl2.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_sndfile.hpp +bin_openmpt123_SOURCES += openmpt123/openmpt123_stdio.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_stdout.hpp +bin_openmpt123_SOURCES += openmpt123/openmpt123_terminal.hpp bin_openmpt123_SOURCES += openmpt123/openmpt123_waveout.hpp man1_MANS = man/openmpt123.1 diff --git a/build/vs2017winxp/openmpt123.vcxproj b/build/vs2017winxp/openmpt123.vcxproj index 7ee8908a58..4ac8b5d7aa 100644 --- a/build/vs2017winxp/openmpt123.vcxproj +++ b/build/vs2017winxp/openmpt123.vcxproj @@ -736,6 +736,7 @@ + @@ -743,7 +744,9 @@ + + diff --git a/build/vs2017winxp/openmpt123.vcxproj.filters b/build/vs2017winxp/openmpt123.vcxproj.filters index 71bc05a374..b544e16ba4 100644 --- a/build/vs2017winxp/openmpt123.vcxproj.filters +++ b/build/vs2017winxp/openmpt123.vcxproj.filters @@ -60,6 +60,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -81,9 +84,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2017winxpansi/openmpt123.vcxproj b/build/vs2017winxpansi/openmpt123.vcxproj index 28d6f3fe11..fbd870b994 100644 --- a/build/vs2017winxpansi/openmpt123.vcxproj +++ b/build/vs2017winxpansi/openmpt123.vcxproj @@ -736,6 +736,7 @@ + @@ -743,7 +744,9 @@ + + diff --git a/build/vs2017winxpansi/openmpt123.vcxproj.filters b/build/vs2017winxpansi/openmpt123.vcxproj.filters index 71bc05a374..b544e16ba4 100644 --- a/build/vs2017winxpansi/openmpt123.vcxproj.filters +++ b/build/vs2017winxpansi/openmpt123.vcxproj.filters @@ -60,6 +60,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -81,9 +84,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2019win7/openmpt123.vcxproj b/build/vs2019win7/openmpt123.vcxproj index 05cebe1063..45cc1ff805 100644 --- a/build/vs2019win7/openmpt123.vcxproj +++ b/build/vs2019win7/openmpt123.vcxproj @@ -733,6 +733,7 @@ + @@ -740,7 +741,9 @@ + + diff --git a/build/vs2019win7/openmpt123.vcxproj.filters b/build/vs2019win7/openmpt123.vcxproj.filters index 05beb59090..acf50b4ad6 100644 --- a/build/vs2019win7/openmpt123.vcxproj.filters +++ b/build/vs2019win7/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2022win10/openmpt123.vcxproj b/build/vs2022win10/openmpt123.vcxproj index 47fec31712..02436baf9c 100644 --- a/build/vs2022win10/openmpt123.vcxproj +++ b/build/vs2022win10/openmpt123.vcxproj @@ -1511,6 +1511,7 @@ + @@ -1518,7 +1519,9 @@ + + diff --git a/build/vs2022win10/openmpt123.vcxproj.filters b/build/vs2022win10/openmpt123.vcxproj.filters index 49f914c1db..2b3bad5e81 100644 --- a/build/vs2022win10/openmpt123.vcxproj.filters +++ b/build/vs2022win10/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2022win10clang/openmpt123.vcxproj b/build/vs2022win10clang/openmpt123.vcxproj index 0cf4a5c472..53b6fc80cd 100644 --- a/build/vs2022win10clang/openmpt123.vcxproj +++ b/build/vs2022win10clang/openmpt123.vcxproj @@ -1429,6 +1429,7 @@ + @@ -1436,7 +1437,9 @@ + + diff --git a/build/vs2022win10clang/openmpt123.vcxproj.filters b/build/vs2022win10clang/openmpt123.vcxproj.filters index 49f914c1db..2b3bad5e81 100644 --- a/build/vs2022win10clang/openmpt123.vcxproj.filters +++ b/build/vs2022win10clang/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2022win7/openmpt123.vcxproj b/build/vs2022win7/openmpt123.vcxproj index dc6e8faccd..00a54c8eb3 100644 --- a/build/vs2022win7/openmpt123.vcxproj +++ b/build/vs2022win7/openmpt123.vcxproj @@ -753,6 +753,7 @@ + @@ -760,7 +761,9 @@ + + diff --git a/build/vs2022win7/openmpt123.vcxproj.filters b/build/vs2022win7/openmpt123.vcxproj.filters index 05beb59090..acf50b4ad6 100644 --- a/build/vs2022win7/openmpt123.vcxproj.filters +++ b/build/vs2022win7/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2022win8/openmpt123.vcxproj b/build/vs2022win8/openmpt123.vcxproj index 0b96dc7f02..5e204a8ae4 100644 --- a/build/vs2022win8/openmpt123.vcxproj +++ b/build/vs2022win8/openmpt123.vcxproj @@ -1123,6 +1123,7 @@ + @@ -1130,7 +1131,9 @@ + + diff --git a/build/vs2022win8/openmpt123.vcxproj.filters b/build/vs2022win8/openmpt123.vcxproj.filters index 690b25580d..b57feab03a 100644 --- a/build/vs2022win8/openmpt123.vcxproj.filters +++ b/build/vs2022win8/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/build/vs2022win81/openmpt123.vcxproj b/build/vs2022win81/openmpt123.vcxproj index 91a6a43a0d..2906c232f9 100644 --- a/build/vs2022win81/openmpt123.vcxproj +++ b/build/vs2022win81/openmpt123.vcxproj @@ -1123,6 +1123,7 @@ + @@ -1130,7 +1131,9 @@ + + diff --git a/build/vs2022win81/openmpt123.vcxproj.filters b/build/vs2022win81/openmpt123.vcxproj.filters index 58840f5cff..7ff1e921be 100644 --- a/build/vs2022win81/openmpt123.vcxproj.filters +++ b/build/vs2022win81/openmpt123.vcxproj.filters @@ -66,6 +66,9 @@ openmpt123 + + openmpt123 + openmpt123 @@ -87,9 +90,15 @@ openmpt123 + + openmpt123 + openmpt123 + + openmpt123 + openmpt123 diff --git a/openmpt123/openmpt123.cpp b/openmpt123/openmpt123.cpp index 69d1bf9bd8..4385ba4588 100644 --- a/openmpt123/openmpt123.cpp +++ b/openmpt123/openmpt123.cpp @@ -78,40 +78,17 @@ static const char * const license = #include #include -#if MPT_OS_DJGPP -#include -#include -#include -#include -#include -#include -#include -#include -#elif MPT_OS_WINDOWS -#include -#include -#include -#include -#if MPT_LIBC_MINGW -#include -#endif -#include -#include -#include +#if MPT_OS_WINDOWS #include #include -#else -#include -#include -#include -#include -#include -#include #endif #include #include "openmpt123.hpp" +#include "openmpt123_exception.hpp" +#include "openmpt123_stdio.hpp" +#include "openmpt123_terminal.hpp" #include "openmpt123_flac.hpp" #include "openmpt123_mmio.hpp" @@ -1107,54 +1084,16 @@ void render_loop( commandlineflags & flags, Tmod & mod, double & duration, texto if ( flags.mode == Mode::UI ) { -#if MPT_OS_DJGPP - - while ( kbhit() ) { - int c = getch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT - - while ( _kbhit() ) { - wint_t c = _getwch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#elif MPT_OS_WINDOWS - - while ( _kbhit() ) { - int c = _getch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#else - - while ( true ) { - pollfd pollfds; - pollfds.fd = STDIN_FILENO; - pollfds.events = POLLIN; - poll(&pollfds, 1, 0); - if ( !( pollfds.revents & POLLIN ) ) { - break; - } - char c = 0; - if ( read( STDIN_FILENO, &c, 1 ) != 1 ) { + while ( terminal_input::is_input_available() ) { + auto c = terminal_input::read_input_char(); + if ( !c ) { break; } - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { + if ( !handle_keypress( *c, flags, mod, audio_stream ) ) { return; } } -#endif - if ( flags.paused ) { audio_stream.sleep( flags.ui_redraw_interval ); continue; @@ -2170,110 +2109,6 @@ static void parse_openmpt123( commandlineflags & flags, const std::vector args ) { FILE_mode_guard stdout_text_guard( stdout, FILE_mode::text ); diff --git a/openmpt123/openmpt123.hpp b/openmpt123/openmpt123.hpp index 2f31569e68..636dfdb52c 100644 --- a/openmpt123/openmpt123.hpp +++ b/openmpt123/openmpt123.hpp @@ -12,6 +12,9 @@ #include "openmpt123_config.hpp" +#include "openmpt123_exception.hpp" +#include "openmpt123_terminal.hpp" + #include "mpt/base/compiletime_warning.hpp" #include "mpt/base/detect.hpp" #include "mpt/base/float.hpp" @@ -63,10 +66,6 @@ struct string_transcoder { namespace openmpt123 { -struct exception : public openmpt::exception { - exception( const mpt::ustring & text ) : openmpt::exception(mpt::transcode( mpt::common_encoding::utf8, text )) { } -}; - struct show_help_exception { mpt::ustring message; bool longhelp; @@ -94,45 +93,6 @@ inline Tstring align_right( const Tchar pad, std::size_t width, const T val ) { return str; } -template -struct concat_stream { - virtual concat_stream & append( Tstring str ) = 0; - virtual ~concat_stream() = default; - inline concat_stream & operator<<( concat_stream & (*func)( concat_stream & s ) ) { - return func( *this ); - } -}; - -template -inline concat_stream & lf( concat_stream & s ) { - return s.append( Tstring(1, mpt::char_constants::lf) ); -} - -template -inline concat_stream & operator<<( concat_stream & s, const T & val ) { - return s.append( mpt::default_formatter::template format( val ) ); -} - -template -struct string_concat_stream - : public concat_stream -{ -private: - Tstring m_str; -public: - inline void str( Tstring s ) { - m_str = std::move( s ); - } - inline concat_stream & append( Tstring s ) override { - m_str += std::move( s ); - return *this; - } - inline Tstring str() const { - return m_str; - } - ~string_concat_stream() override = default; -}; - struct field { mpt::ustring key; @@ -140,260 +100,6 @@ struct field { }; -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -inline bool IsConsole( DWORD stdHandle ) { - HANDLE hStd = GetStdHandle( stdHandle ); - if ( ( hStd != NULL ) && ( hStd != INVALID_HANDLE_VALUE ) ) { - DWORD mode = 0; - if ( GetConsoleMode( hStd, &mode ) != FALSE ) { - return true; - } - } - return false; -} -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - -inline bool IsTerminal( int fd ) { -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - if ( !_isatty( fd ) ) { - return false; - } - DWORD stdHandle = 0; - if ( fd == 0 ) { - stdHandle = STD_INPUT_HANDLE; - } else if ( fd == 1 ) { - stdHandle = STD_OUTPUT_HANDLE; - } else if ( fd == 2 ) { - stdHandle = STD_ERROR_HANDLE; - } - return IsConsole( stdHandle ); -#else - return isatty( fd ) ? true : false; -#endif -} - - -class textout : public string_concat_stream { -protected: - textout() = default; -public: - virtual ~textout() = default; -protected: - mpt::ustring pop() { - mpt::ustring text = str(); - str( mpt::ustring() ); - return text; - } -public: - virtual void writeout() = 0; - virtual void cursor_up( std::size_t lines ) = 0; -}; - - - -class textout_dummy : public textout { -public: - textout_dummy() = default; - ~textout_dummy() override { - static_cast( pop() ); - } -public: - void writeout() override { - static_cast( pop() ); - } - void cursor_up( std::size_t lines ) override { - static_cast( lines ); - } -}; - - - -enum class textout_destination { - destination_stdout, - destination_stderr, -}; - -class textout_backend { -protected: - textout_backend() = default; -public: - virtual ~textout_backend() = default; -public: - virtual void write( const mpt::ustring & text ) = 0; - virtual void cursor_up(std::size_t lines) = 0; -}; - - - -class textout_ostream : public textout_backend { -private: - std::ostream & s; -#if MPT_OS_DJGPP - mpt::common_encoding codepage; -#endif -public: - textout_ostream( std::ostream & s_ ) - : s(s_) -#if MPT_OS_DJGPP - , codepage(mpt::common_encoding::cp437) -#endif - { - #if MPT_OS_DJGPP - codepage = mpt::djgpp_get_locale_encoding(); - #endif - return; - } - ~textout_ostream() override = default; -public: - void write( const mpt::ustring & text ) override { - if ( text.length() > 0 ) { - #if MPT_OS_DJGPP - s << mpt::transcode( codepage, text ); - #elif MPT_OS_EMSCRIPTEN - s << mpt::transcode( mpt::common_encoding::utf8, text ) ; - #else - s << mpt::transcode( mpt::logical_encoding::locale, text ); - #endif - s.flush(); - } - } - void cursor_up( std::size_t lines ) override { - s.flush(); - for ( std::size_t line = 0; line < lines; ++line ) { - s << std::string("\x1b[1A"); - } - } -}; - -#if MPT_OS_WINDOWS && defined(UNICODE) - -class textout_wostream : public textout_backend { -private: - std::wostream & s; -public: - textout_wostream( std::wostream & s_ ) - : s(s_) - { - return; - } - ~textout_wostream() override = default; -public: - void write( const mpt::ustring & text ) override { - if ( text.length() > 0 ) { - s << mpt::transcode( text ); - s.flush(); - } - } - void cursor_up( std::size_t lines ) override { - s.flush(); - for ( std::size_t line = 0; line < lines; ++line ) { - s << std::wstring(L"\x1b[1A"); - } - } -}; - -#endif // MPT_OS_WINDOWS && UNICODE - -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - -class textout_ostream_console : public textout_backend { -private: -#if defined(UNICODE) - std::wostream & s; -#else - std::ostream & s; -#endif - HANDLE handle; - bool console; -public: -#if defined(UNICODE) - textout_ostream_console( std::wostream & s_, DWORD stdHandle_ ) -#else - textout_ostream_console( std::ostream & s_, DWORD stdHandle_ ) -#endif - : s(s_) - , handle(GetStdHandle( stdHandle_ )) - , console(IsConsole( stdHandle_ )) - { - return; - } - ~textout_ostream_console() override = default; -public: - void write( const mpt::ustring & text ) override { - if ( text.length() > 0 ) { - if ( console ) { - DWORD chars_written = 0; - #if defined(UNICODE) - std::wstring wtext = mpt::transcode( text ); - WriteConsole( handle, wtext.data(), static_cast( wtext.size() ), &chars_written, NULL ); - #else - std::string ltext = mpt::transcode( mpt::logical_encoding::locale, text ); - WriteConsole( handle, ltext.data(), static_cast( ltext.size() ), &chars_written, NULL ); - #endif - } else { - #if defined(UNICODE) - s << mpt::transcode( text ); - #else - s << mpt::transcode( mpt::logical_encoding::locale, text ); - #endif - s.flush(); - } - } - } - void cursor_up( std::size_t lines ) override { - if ( console ) { - s.flush(); - CONSOLE_SCREEN_BUFFER_INFO csbi; - ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); - COORD coord_cursor = COORD(); - if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) { - coord_cursor = csbi.dwCursorPosition; - coord_cursor.X = 1; - coord_cursor.Y -= static_cast( lines ); - SetConsoleCursorPosition( handle, coord_cursor ); - } - } - } -}; - -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - - - -template -class textout_wrapper : public textout { -private: -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -#if defined(UNICODE) - textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; -#else - textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::cout : std::clog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; -#endif -#elif MPT_OS_WINDOWS -#if defined(UNICODE) - textout_wostream out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog }; -#else - textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; -#endif -#else - textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; -#endif -public: - textout_wrapper() = default; - ~textout_wrapper() override { - out.write( pop() ); - } -public: - void writeout() override { - out.write( pop() ); - } - void cursor_up(std::size_t lines) override { - out.cursor_up( lines ); - } -}; - - - inline mpt::ustring append_software_tag( mpt::ustring software ) { mpt::ustring openmpt123 = mpt::ustring() + MPT_USTRING("openmpt123 ") @@ -540,78 +246,7 @@ struct commandlineflags { canProgress = isatty( STDERR_FILENO ) ? true : false; #endif // MPT_OS_WINDOWS } -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); - if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) { - CONSOLE_SCREEN_BUFFER_INFO csbi; - ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); - if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) { - if ( terminal_width <= 0 ) { - terminal_width = std::min( static_cast( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast( csbi.dwSize.X ) ); - } - if ( terminal_height <= 0 ) { - terminal_height = std::min( static_cast( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast( csbi.dwSize.Y ) ); - } - } - } -#else // !(MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10)) - if ( isatty( STDERR_FILENO ) ) { - if ( terminal_width <= 0 ) { - const char * env_columns = std::getenv( "COLUMNS" ); - if ( env_columns ) { - int tmp = mpt::parse_or( env_columns, 0 ); - if ( tmp > 0 ) { - terminal_width = tmp; - } - } - } - if ( terminal_height <= 0 ) { - const char * env_rows = std::getenv( "ROWS" ); - if ( env_rows ) { - int tmp = mpt::parse_or( env_rows, 0 ); - if ( tmp > 0 ) { - terminal_height = tmp; - } - } - } - #if defined(TIOCGWINSZ) - struct winsize ts; - if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) { - if ( terminal_width <= 0 ) { - terminal_width = ts.ws_col; - } - if ( terminal_height <= 0 ) { - terminal_height = ts.ws_row; - } - } - #elif defined(TIOCGSIZE) - struct ttysize ts; - if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) { - if ( terminal_width <= 0 ) { - terminal_width = ts.ts_cols; - } - if ( terminal_height <= 0 ) { - terminal_height = ts.ts_rows; - } - } - #endif - } -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -#if MPT_OS_DJGPP - if ( terminal_width <= 0 ) { - terminal_width = 80; - } - if ( terminal_height <= 0 ) { - terminal_height = 25; - } -#else - if ( terminal_width <= 0 ) { - terminal_width = 72; - } - if ( terminal_height <= 0 ) { - terminal_height = 23; - } -#endif + query_terminal_size( terminal_width, terminal_height ); if ( filenames.size() == 0 ) { throw args_nofiles_exception(); } diff --git a/openmpt123/openmpt123_exception.hpp b/openmpt123/openmpt123_exception.hpp new file mode 100644 index 0000000000..79e23c54e1 --- /dev/null +++ b/openmpt123/openmpt123_exception.hpp @@ -0,0 +1,30 @@ +/* + * openmpt123_exception.hpp + * ------------------------ + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_EXCEPTION_HPP +#define OPENMPT123_EXCEPTION_HPP + +#include "openmpt123_config.hpp" + +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include + +#include + +namespace openmpt123 { + +struct exception : public openmpt::exception { + exception( const mpt::ustring & text ) : openmpt::exception(mpt::transcode( mpt::common_encoding::utf8, text )) { } +}; + +} // namespace openmpt123 + +#endif // OPENMPT123_EXCEPTION_HPP diff --git a/openmpt123/openmpt123_stdio.hpp b/openmpt123/openmpt123_stdio.hpp new file mode 100644 index 0000000000..58efd81983 --- /dev/null +++ b/openmpt123/openmpt123_stdio.hpp @@ -0,0 +1,98 @@ +/* + * openmpt123_stdio.hpp + * -------------------- + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_STDIO_HPP +#define OPENMPT123_STDIO_HPP + +#include "openmpt123_config.hpp" + +#include "openmpt123_exception.hpp" + +#include "mpt/base/detect.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/string/types.hpp" + +#include + +#if MPT_OS_WINDOWS +#include +#include +#include +#endif + +namespace openmpt123 { + +enum class FILE_mode { + text, + binary, +}; + +#if MPT_OS_WINDOWS + +class FILE_mode_guard { +private: + FILE * file; + int old_mode; +public: + FILE_mode_guard( FILE * file, FILE_mode new_mode ) + : file(file) + , old_mode(-1) + { + switch (new_mode) { + case FILE_mode::text: + fflush( file ); + #if defined(UNICODE) + old_mode = _setmode( _fileno( file ), _O_U8TEXT ); + #else + old_mode = _setmode( _fileno( file ), _O_TEXT ); + #endif + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set TEXT mode on file descriptor") ); + } + break; + case FILE_mode::binary: + fflush( file ); + old_mode = _setmode( _fileno( file ), _O_BINARY ); + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set binary mode on file descriptor") ); + } + break; + } + } + FILE_mode_guard( const FILE_mode_guard & ) = delete; + FILE_mode_guard( FILE_mode_guard && ) = default; + FILE_mode_guard & operator=( const FILE_mode_guard & ) = delete; + FILE_mode_guard & operator=( FILE_mode_guard && ) = default; + ~FILE_mode_guard() { + if ( old_mode != -1 ) { + fflush( file ); + old_mode = _setmode( _fileno( file ), old_mode ); + } + } +}; + +#else + +class FILE_mode_guard { +public: + FILE_mode_guard( FILE * /* file */, FILE_mode /* new_mode */ ) { + return; + } + FILE_mode_guard( const FILE_mode_guard & ) = delete; + FILE_mode_guard( FILE_mode_guard && ) = default; + FILE_mode_guard & operator=( const FILE_mode_guard & ) = delete; + FILE_mode_guard & operator=( FILE_mode_guard && ) = default; + ~FILE_mode_guard() = default; +}; + +#endif + +} // namespace openmpt123 + +#endif // OPENMPT123_STDIO_HPP diff --git a/openmpt123/openmpt123_terminal.hpp b/openmpt123/openmpt123_terminal.hpp new file mode 100644 index 0000000000..e9f83d1017 --- /dev/null +++ b/openmpt123/openmpt123_terminal.hpp @@ -0,0 +1,517 @@ +/* + * openmpt123_terminal.hpp + * ----------------------- + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_TERMINAL_HPP +#define OPENMPT123_TERMINAL_HPP + +#include "openmpt123_config.hpp" + +#include "mpt/base/detect.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/format/simple.hpp" +#include "mpt/parse/parse.hpp" +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include +#include +#include +#include + +#include + +#if MPT_OS_DJGPP +#include +#include +#include +#include +#include +#include +#include +#include +#elif MPT_OS_WINDOWS +#include +#include +#include +#include +#if MPT_LIBC_MINGW +#include +#endif +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace openmpt123 { + +template +struct concat_stream { + virtual concat_stream & append( Tstring str ) = 0; + virtual ~concat_stream() = default; + inline concat_stream & operator<<( concat_stream & (*func)( concat_stream & s ) ) { + return func( *this ); + } +}; + +template +inline concat_stream & lf( concat_stream & s ) { + return s.append( Tstring(1, mpt::char_constants::lf) ); +} + +template +inline concat_stream & operator<<( concat_stream & s, const T & val ) { + return s.append( mpt::default_formatter::template format( val ) ); +} + +template +struct string_concat_stream + : public concat_stream +{ +private: + Tstring m_str; +public: + inline void str( Tstring s ) { + m_str = std::move( s ); + } + inline concat_stream & append( Tstring s ) override { + m_str += std::move( s ); + return *this; + } + inline Tstring str() const { + return m_str; + } + ~string_concat_stream() override = default; +}; + + +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +inline bool IsConsole( DWORD stdHandle ) { + HANDLE hStd = GetStdHandle( stdHandle ); + if ( ( hStd != NULL ) && ( hStd != INVALID_HANDLE_VALUE ) ) { + DWORD mode = 0; + if ( GetConsoleMode( hStd, &mode ) != FALSE ) { + return true; + } + } + return false; +} +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + +inline bool IsTerminal( int fd ) { +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + if ( !_isatty( fd ) ) { + return false; + } + DWORD stdHandle = 0; + if ( fd == 0 ) { + stdHandle = STD_INPUT_HANDLE; + } else if ( fd == 1 ) { + stdHandle = STD_OUTPUT_HANDLE; + } else if ( fd == 2 ) { + stdHandle = STD_ERROR_HANDLE; + } + return IsConsole( stdHandle ); +#else + return isatty( fd ) ? true : false; +#endif +} + + +class textout : public string_concat_stream { +protected: + textout() = default; +public: + virtual ~textout() = default; +protected: + mpt::ustring pop() { + mpt::ustring text = str(); + str( mpt::ustring() ); + return text; + } +public: + virtual void writeout() = 0; + virtual void cursor_up( std::size_t lines ) = 0; +}; + + + +class textout_dummy : public textout { +public: + textout_dummy() = default; + ~textout_dummy() override { + static_cast( pop() ); + } +public: + void writeout() override { + static_cast( pop() ); + } + void cursor_up( std::size_t lines ) override { + static_cast( lines ); + } +}; + + + +enum class textout_destination { + destination_stdout, + destination_stderr, +}; + +class textout_backend { +protected: + textout_backend() = default; +public: + virtual ~textout_backend() = default; +public: + virtual void write( const mpt::ustring & text ) = 0; + virtual void cursor_up(std::size_t lines) = 0; +}; + + + +class textout_ostream : public textout_backend { +private: + std::ostream & s; +#if MPT_OS_DJGPP + mpt::common_encoding codepage; +#endif +public: + textout_ostream( std::ostream & s_ ) + : s(s_) +#if MPT_OS_DJGPP + , codepage(mpt::common_encoding::cp437) +#endif + { + #if MPT_OS_DJGPP + codepage = mpt::djgpp_get_locale_encoding(); + #endif + return; + } + ~textout_ostream() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + #if MPT_OS_DJGPP + s << mpt::transcode( codepage, text ); + #elif MPT_OS_EMSCRIPTEN + s << mpt::transcode( mpt::common_encoding::utf8, text ) ; + #else + s << mpt::transcode( mpt::logical_encoding::locale, text ); + #endif + s.flush(); + } + } + void cursor_up( std::size_t lines ) override { + s.flush(); + for ( std::size_t line = 0; line < lines; ++line ) { + s << std::string("\x1b[1A"); + } + } +}; + +#if MPT_OS_WINDOWS && defined(UNICODE) + +class textout_wostream : public textout_backend { +private: + std::wostream & s; +public: + textout_wostream( std::wostream & s_ ) + : s(s_) + { + return; + } + ~textout_wostream() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + s << mpt::transcode( text ); + s.flush(); + } + } + void cursor_up( std::size_t lines ) override { + s.flush(); + for ( std::size_t line = 0; line < lines; ++line ) { + s << std::wstring(L"\x1b[1A"); + } + } +}; + +#endif // MPT_OS_WINDOWS && UNICODE + +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + +class textout_ostream_console : public textout_backend { +private: +#if defined(UNICODE) + std::wostream & s; +#else + std::ostream & s; +#endif + HANDLE handle; + bool console; +public: +#if defined(UNICODE) + textout_ostream_console( std::wostream & s_, DWORD stdHandle_ ) +#else + textout_ostream_console( std::ostream & s_, DWORD stdHandle_ ) +#endif + : s(s_) + , handle(GetStdHandle( stdHandle_ )) + , console(IsConsole( stdHandle_ )) + { + return; + } + ~textout_ostream_console() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + if ( console ) { + DWORD chars_written = 0; + #if defined(UNICODE) + std::wstring wtext = mpt::transcode( text ); + WriteConsole( handle, wtext.data(), static_cast( wtext.size() ), &chars_written, NULL ); + #else + std::string ltext = mpt::transcode( mpt::logical_encoding::locale, text ); + WriteConsole( handle, ltext.data(), static_cast( ltext.size() ), &chars_written, NULL ); + #endif + } else { + #if defined(UNICODE) + s << mpt::transcode( text ); + #else + s << mpt::transcode( mpt::logical_encoding::locale, text ); + #endif + s.flush(); + } + } + } + void cursor_up( std::size_t lines ) override { + if ( console ) { + s.flush(); + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + COORD coord_cursor = COORD(); + if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) { + coord_cursor = csbi.dwCursorPosition; + coord_cursor.X = 1; + coord_cursor.Y -= static_cast( lines ); + SetConsoleCursorPosition( handle, coord_cursor ); + } + } + } +}; + +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + + + +template +class textout_wrapper : public textout { +private: +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +#if defined(UNICODE) + textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; +#else + textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::cout : std::clog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; +#endif +#elif MPT_OS_WINDOWS +#if defined(UNICODE) + textout_wostream out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog }; +#else + textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; +#endif +#else + textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; +#endif +public: + textout_wrapper() = default; + ~textout_wrapper() override { + out.write( pop() ); + } +public: + void writeout() override { + out.write( pop() ); + } + void cursor_up(std::size_t lines) override { + out.cursor_up( lines ); + } +}; + + +inline void query_terminal_size( int & terminal_width, int & terminal_height ) { +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) { + if ( terminal_width <= 0 ) { + terminal_width = std::min( static_cast( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast( csbi.dwSize.X ) ); + } + if ( terminal_height <= 0 ) { + terminal_height = std::min( static_cast( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast( csbi.dwSize.Y ) ); + } + } + } +#else // !(MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10)) + if ( isatty( STDERR_FILENO ) ) { + if ( terminal_width <= 0 ) { + const char * env_columns = std::getenv( "COLUMNS" ); + if ( env_columns ) { + int tmp = mpt::parse_or( env_columns, 0 ); + if ( tmp > 0 ) { + terminal_width = tmp; + } + } + } + if ( terminal_height <= 0 ) { + const char * env_rows = std::getenv( "ROWS" ); + if ( env_rows ) { + int tmp = mpt::parse_or( env_rows, 0 ); + if ( tmp > 0 ) { + terminal_height = tmp; + } + } + } + #if defined(TIOCGWINSZ) + struct winsize ts; + if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) { + if ( terminal_width <= 0 ) { + terminal_width = ts.ws_col; + } + if ( terminal_height <= 0 ) { + terminal_height = ts.ws_row; + } + } + #elif defined(TIOCGSIZE) + struct ttysize ts; + if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) { + if ( terminal_width <= 0 ) { + terminal_width = ts.ts_cols; + } + if ( terminal_height <= 0 ) { + terminal_height = ts.ts_rows; + } + } + #endif + } +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +#if MPT_OS_DJGPP + if ( terminal_width <= 0 ) { + terminal_width = 80; + } + if ( terminal_height <= 0 ) { + terminal_height = 25; + } +#else + if ( terminal_width <= 0 ) { + terminal_width = 72; + } + if ( terminal_height <= 0 ) { + terminal_height = 23; + } +#endif +} + + +#if MPT_OS_WINDOWS + +class terminal_ui_guard { +public: + terminal_ui_guard() = default; + terminal_ui_guard( const terminal_ui_guard & ) = delete; + terminal_ui_guard( terminal_ui_guard && ) = default; + terminal_ui_guard & operator=( const terminal_ui_guard & ) = delete; + terminal_ui_guard & operator=( terminal_ui_guard && ) = default; + ~terminal_ui_guard() = default; +}; + +#else + +class terminal_ui_guard { +private: + bool changed = false; + termios saved_attributes; +public: + terminal_ui_guard() { + if ( !isatty( STDIN_FILENO ) ) { + return; + } + tcgetattr( STDIN_FILENO, &saved_attributes ); + termios tattr = saved_attributes; + tattr.c_lflag &= ~( ICANON | ECHO ); + tattr.c_cc[VMIN] = 1; + tattr.c_cc[VTIME] = 0; + tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr ); + changed = true; + } + terminal_ui_guard( const terminal_ui_guard & ) = delete; + terminal_ui_guard( terminal_ui_guard && ) = default; + terminal_ui_guard & operator=( const terminal_ui_guard & ) = delete; + terminal_ui_guard & operator=( terminal_ui_guard && ) = default; + ~terminal_ui_guard() { + if ( changed ) { + tcsetattr(STDIN_FILENO, TCSANOW, &saved_attributes); + } + } +}; + +#endif + + +class terminal_input { +public: + static inline bool is_input_available() { +#if MPT_OS_DJGPP + return kbhit() ? true : false; +#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT + return _kbhit() ? true : false; +#elif MPT_OS_WINDOWS + return _kbhit() ? true : false; +#else + pollfd pollfds; + pollfds.fd = STDIN_FILENO; + pollfds.events = POLLIN; + poll(&pollfds, 1, 0); + if ( !( pollfds.revents & POLLIN ) ) { + return false; + } + return true; +#endif + } + static inline std::optional read_input_char() { +#if MPT_OS_DJGPP + int c = getch(); + return static_cast( c ); +#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT + wint_t c = _getwch(); + return static_cast( c ); +#elif MPT_OS_WINDOWS + int c = _getch(); + return static_cast( c ); +#else + char c = 0; + if ( read( STDIN_FILENO, &c, 1 ) != 1 ) { + return std::nullopt; + } + return static_cast( c ); +#endif + } +}; + + +} // namespace openmpt123 + +#endif // OPENMPT123_TERMINAL_HPP