Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Request for comments about this console window interface

66 views
Skip to first unread message

Alf P. Steinbach

unread,
May 13, 2016, 8:46:43 PM5/13/16
to
I've only implemented this for Windows, not for Unixland, and it's been
some time since I used e.g. ncurses. The intent is to support the simple
kind of stuff that Indian students now use Turbo C++ in DOSBox for. E.g.
snake game, that kind of thing, what we once did on IBM PC.

So, how practical/impractical is it wrt. implementation for Unix-land,
e.g. in terms of ncurses? I'm thinking, maybe ncurses can't turn off
automatic line wrapping or automatic scrolling, or maybe all necessary
info isn't available. Or maybe (I seem to remember) different colors?

Other comments?

Cheers!,

- Alf

PS: Sorry for the pasted code rather than more easily extracted
attachments, but the Eternal September server didn't like the
attachments. It mumbled something about "binary". Maybe Thunderbird
encoded as base64, I couldn't find anything about it in options.


<file reusable/console/text_screen.impl.windows.hpp>
#pragma once
// Copyright © 2016 Alf P. Steinbach

#include "text_screen.decl.hpp"

#ifndef _WIN32
# error This header is only for Windows.
#endif

#include "text_screen.impl.windows/winapi.hpp"

#include <assert.h> // assert
#include <bitset> // std::bitset
#include <stdlib.h> // abort, atexit, at_quick_exit

#include <io.h> // _open_osfhandle, _dup2
#include <fcntl.h> // _O_WRONLY

namespace console {
using cppx::Contiguous_enumeration;
using cppx::hopefully;
using cppx::fail;
using cppx::invoke;
using cppx::length;
using cppx::operator<<;
using cppx::Ptr_;
using cppx::String;
using cppx::trace;

using std::bitset;

namespace impl{
inline auto screen_buffer_to_restore()
-> Ref_<winapi::Console_handle>
{
static winapi::Console_handle the_buffer_handle = 0;
return the_buffer_handle;
}

inline void set_default_screen_buffer_to_restore()
{
assert( screen_buffer_to_restore() == 0 );
screen_buffer_to_restore() = winapi::original_screen_buffer();
}

inline void restore_screen_buffer()
{
if( screen_buffer_to_restore() != 0 )
{
winapi::SetConsoleActiveScreenBuffer(
screen_buffer_to_restore() )
|| fail( "restore_screen_buffer:
SetConsoleActiveScreenBuffer failed" );
screen_buffer_to_restore() = 0;
}
}

inline void set_automatic_buffer_restore_on_some_bad_happenings()
{
// This is just a best effort to restore the console on
program termination.
// A crash will bypass all these measures, probably leaving
this particular
// console window unusable (it can still be closed, but
that's all).
static const bool dummy = invoke( []() -> bool
{
// console signal handler:
static const auto ctrl_handler = [](
const winapi::Console_signal::Enum signal
) -> winapi::Bool
{
switch( signal )
{
case winapi::Console_signal::ctrl_c:
case winapi::Console_signal::ctrl_break:
restore_screen_buffer();
break;
default: {}
}
return false; // Invoke next or default handler.
};
winapi::SetConsoleCtrlHandler( ctrl_handler, true );

// exit handler:
atexit( &restore_screen_buffer );

// quick exit handler:
#if CPPX_STDLIB_HAS_QUICK_EXIT
at_quick_exit( &restore_screen_buffer );
#endif

// terminate handler:
static const std::terminate_handler old_handler =
std::get_terminate();
std::set_terminate( []()
{
restore_screen_buffer();
const auto handler = old_handler;
handler? handler() : abort();
} );
return false;
} );

(void) dummy;
}

const bool
call_set_automatic_buffer_restore_on_some_bad_happenings =
(set_automatic_buffer_restore_on_some_bad_happenings(), true);
} // namespace impl

inline Color::Color( const Colorbit::Enum bitnum )
: value( 1 << bitnum )
{}

class Display::Impl
: public Non_copyable
, public Non_movable
{
private:
using Api_info = impl::winapi::Console_screen_buffer_info;
using Api_info_ex = impl::winapi::Console_screen_buffer_info_ex;
using Api_handle = impl::winapi::Console_handle;

struct Text_buffer
: Non_copyable
, Non_movable
{
Api_handle handle;

~Text_buffer()
{ impl::winapi::CloseHandle( handle ); }

Text_buffer()
: handle( impl::winapi::make_screen_buffer() )
{}
};

class Settings
: public Non_copyable
, public Non_movable
{
public:
struct Item_id_values
{
enum Enum
{
size, position, attributes, window_rect,
max_window_size, _
};
};
using Item_id = Contiguous_enumeration<Item_id_values>;

private:
const Api_handle text_buffer_handle_;
bitset<Item_id::n_values()> stale_items_;
Api_info_ex api_settings_ex_ = { sizeof(
Api_info_ex ) };

CPPX_STATIC_ASSERT( offsetof( Api_info_ex, struct_size ) ==
0 );

// Coord dwSize;
// Coord dwCursorPosition;
// Word wAttributes;
// Small_rect srWindow;
// Coord dwMaximumWindowSize;

public:
void update_api_info()
{
trace( L"Settings::update_api_info" );
impl::winapi::GetConsoleScreenBufferInfo(
text_buffer_handle_, &api_settings_ex_.data
)
|| fail( "GetConsoleScreenBufferInfo failed" );
stale_items_.reset();
}

void update_stale_api_info_items()
{
if( stale_items_.count() > 0 )
{
// TODO: optimize?
update_api_info();
}
}

auto api_info()
-> Ref_<const Api_info>
{
update_stale_api_info_items();
return api_settings_ex_.data;
}

auto mutable_api_info()
-> Ref_<Api_info>
{
update_stale_api_info_items();
return api_settings_ex_.data;
}

void invalidate( const Item_id::Enum which )
{ stale_items_.set( which ); }

void validate( const Item_id::Enum which )
{ stale_items_.reset( which ); }

void make_real( Ref_<const Api_info> info )
{
Api_info_ex new_info = api_settings_ex_;
new_info.data = info;
//trace( String() << L"1: " << info.srWindow.right <<
L", " << info.srWindow.bottom );
impl::winapi::SetConsoleScreenBufferInfoEx(
text_buffer_handle_, &new_info
)
|| fail( "SetConsoleScreenBufferInfoEx failed" );
update_api_info();
// auto& info2 = api_settings_ex_.data;
// trace( String() << L"2: " << info2.srWindow.right <<
L", " << info2.srWindow.bottom );
}

Settings( const Api_handle h )
: text_buffer_handle_( h )
{
impl::winapi::GetConsoleScreenBufferInfoEx(
text_buffer_handle_, &api_settings_ex_
)
|| fail( "GetConsoleScreenBufferInfoEx failed" );
stale_items_.reset();
}
};

struct Text_buffer_activation
: Non_copyable
, Non_movable
{
~Text_buffer_activation()
{ impl::restore_screen_buffer(); }

Text_buffer_activation(
const impl::winapi::Console_handle text_buffer_handle
)
{
impl::set_default_screen_buffer_to_restore();
const bool activated =
!!impl::winapi::SetConsoleActiveScreenBuffer(
text_buffer_handle
);
if( not activated )
{
impl::screen_buffer_to_restore() = 0;
fail( "Display::<init>:
SetActiveConsoleScreenBuffer failed" );
}
}
};

// State:
Text_buffer text_buffer_;
mutable Settings settings_;
Text_buffer_activation text_buffer_activation_;

public:
auto size() const
-> Point
{
auto const pt = settings_.api_info().dwSize;
return Point{ pt.x, pt.y };
}

auto window_size() const
-> Point
{
const auto r = settings_.api_info().srWindow;
return Point{ 1 + r.right - r.left, 1 + r.bottom - r.top };
}

void gotoxy( const Point pos )
{
// TODO: optimize by deferring?
namespace api = impl::winapi;
const api::Coord coord = {short( pos.x ), short( pos.y )};
impl::winapi::SetConsoleCursorPosition(
text_buffer_.handle, coord )
|| fail( "gotoxy: SetConsoleCursorPosition failed" );
settings_.validate( Settings::Item_id::position ); //
Avoid update.
settings_.mutable_api_info().dwCursorPosition = coord;
}

auto textcolor() const
-> Color
{ return Color( settings_.api_info().wAttributes & 0x0F ); }

void set_textcolor( const Color c )
{
auto colorbits = settings_.api_info().wAttributes;
colorbits = static_cast<impl::winapi::Word>( colorbits &
0xF0 | c.value );
impl::winapi::SetConsoleTextAttribute( text_buffer_.handle,
colorbits )
|| fail( "set_textcolor: SetConsoleTextAttribute failed" );
settings_.mutable_api_info().wAttributes = colorbits;
settings_.validate( Settings::Item_id::attributes );
}

auto bgcolor() const
-> Color
{ return Color( settings_.api_info().wAttributes >> 4 ); }

void set_bgcolor( const Color c )
{
auto colorbits = settings_.api_info().wAttributes;
colorbits = static_cast<impl::winapi::Word>( colorbits &
0x0F | (c.value << 4) );
impl::winapi::SetConsoleTextAttribute( text_buffer_.handle,
colorbits )
|| fail( "set_textcolor: SetConsoleTextAttribute failed" );
settings_.mutable_api_info().wAttributes = colorbits;
settings_.validate( Settings::Item_id::attributes );
}

void write( Ref_<const String> s )
{
namespace api = impl::winapi;
const int n = length( s );
if( n == 0 ) { return; }
settings_.invalidate( Settings::Item_id::position );
api::Double_word n_written = 0;
api::WriteConsoleW( text_buffer_.handle, s.data(), n,
&n_written, nullptr )
|| fail( "Display::write: WriteConsoleW failed" );
assert( int( n_written ) == n );
}

~Impl()
{}

Impl( const Point size )
: text_buffer_()
, settings_( text_buffer_.handle )
, text_buffer_activation_( text_buffer_.handle )
{
{ // with
Api_info params = settings_.api_info();

params.dwSize = {short( size.x ), short( size.y )};
params.dwCursorPosition = {};
//screen.wAttributes = bitsForBgFg( // TODO:

// The window size specified here is 1 too large, to
avoid scrollbars
// appearing when the original window is smaller in any
dimension. It
// works in Windows 7. Just a workaround based on
undocumented behavior.
params.srWindow = {0, 0, short( size.x ), short( size.y )};
params.dwMaximumWindowSize = params.dwSize;
settings_.make_real( params );
}

namespace api = impl::winapi;
using Flag = api::Output_flags;
api::set_output_mode(
text_buffer_.handle, Flag::processed_output &
~Flag::wrap_at_eol
);

{
api::Cursor_info params = { 1, false };
api::SetConsoleCursorInfo( text_buffer_.handle, &params )
|| fail( "Display::<init>: SetConsoleCursorInfo
failed" );
}

// Using the following code (it's not called here) causes
delayed
// bluescreen in Windows 7. Possibly a guaranteed cleanup
may fix it.
const auto text_buffer_handle = text_buffer_.handle;
(void) [text_buffer_handle]() -> void
{
const int fd = _open_osfhandle(
reinterpret_cast<intptr_t>( text_buffer_handle ),
_O_WRONLY
);
hopefully( fd != -1 )
|| fail( "console::Display::<init>: _open_osfhandle
failed" );
const int dup_result = _dup2( fd, _fileno( stdout ) );

// _close( fd ); // ! DON'T CLOSE this. Or, possible
bluescreen.
hopefully( dup_result != -1 )
|| fail( "console::Display::<init>: _dup2 failed" );
};
}
};

inline auto Display::size() const -> Point
{ return p_impl_->size(); }

inline auto Display::position() const -> Point
{ return {}; } // TODO:

inline void Display::gotoxy( const Point pos )
{ return p_impl_->gotoxy( pos ); }

inline auto Display::textcolor() const -> Color
{ return p_impl_->textcolor(); }

inline void Display::set_textcolor( const Color c )
{ p_impl_->set_textcolor( c ); }

inline auto Display::bgcolor() const -> Color
{ return p_impl_->bgcolor(); }

inline void Display::set_bgcolor( const Color c )
{ p_impl_->set_bgcolor( c ); }

inline void Display::write( Ref_<const String> s )
{ p_impl_->write( s ); }

void Display::clear()
{
// TODO: optimize?
clear_by_writing_spaces();
}

inline Display::~Display()
{ delete p_impl_; }

inline Display::Display(
Ref_<const String> title,
const Point size
)
: p_impl_( new Impl( size ) )
{
impl::winapi::SetConsoleTitleW( title.c_str() )
|| fail( "SetConsoleTitle failed" );
}

} // namespace console
</file>

<file reusable/cppx/basics.hpp>
#pragma once
// Copyright © 2016 Alf P. Steinbach
//
// Configuration macro symbols:
// • CPPX_NO_IOSTREAMS Don't drag in the iostreams header,
please.
// • CPPX_STDLIB_HAS_QUICK_EXIT Force assumption that
std::quick_exit exists.
// • NDEBUG Standard meaning (here: suppress
tracing).

#include <assert.h> // assert
#ifndef CPPX_NO_IOSTREAMS
# include <iostream> // std::(wistream, wostream, wcin, wcout,
wcerr, endl)
#endif
#include <locale.h> // setlocale, LC_ALL
#include <stddef.h> // EXIT_FAILURE, EXIT_SUCCESS, ptrdiff_t
#include <stdexcept> // std::(exception, runtime_error)
#include <string> // std::(string, wstring)
#include <type_traits> // std::underlying_type
#include <wctype.h> // towupper

#if !defined( CPPX_STDLIB_HAS_QUICK_EXIT )
# if defined( __GNUC__ )
# if defined( _GLIBCXX_HAVE_AT_QUICK_EXIT )
# define CPPX_STDLIB_HAS_QUICK_EXIT _GLIBCXX_HAVE_AT_QUICK_EXIT
# else
# define CPPX_STDLIB_HAS_QUICK_EXIT 0
# endif
# else
# define CPPX_STDLIB_HAS_QUICK_EXIT 1
# endif
#endif

#define CPPX_STATIC_ASSERT( e ) static_assert( e, #e " // <- Is required!" )

#define CPX_WITH_BITLEVEL_OPS \
using U = std::underlying_type_t< Enum >; \
\
friend auto operator|( const Enum a, const Enum b ) \
-> Enum \
{ return Enum( U( a ) | U( b ) ); } \
\
friend auto operator&( const Enum a, const Enum b ) \
-> Enum \
{ return Enum( U( a ) & U( b ) ); } \
\
friend auto operator~( const Enum a ) \
-> Enum \
{ return Enum( ~U( a ) ); }

// General reusable support:
namespace cppx {
using std::endl;
using std::runtime_error;
using Size = ptrdiff_t;

template< class Type >
using Ref_ = Type&; // To write `Ref_<Sometype>` instead of
`Sometype&`.

template< class Type >
using Rvref_ = Type&&; // To write `Rvref_<Sometype>` instead of
`Sometype&&`.

template< class Type >
using Ptr_ = Type*; // To write `Ptr_<Sometype>` instead of
`Sometype*`.

template< class Values_wrapper >
struct Contiguous_enumeration: Values_wrapper
{
static constexpr auto n_values() -> int { return
Values_wrapper::_; }
};

class Non_copyable
{
private:
Non_copyable( Ref_<const Non_copyable> ) = delete;
auto operator=( Ref_<const Non_copyable> ) ->
Ref_<Non_copyable> = delete;
public:
auto operator=( Rvref_<Non_copyable> )
-> Ref_<Non_copyable>
{ return *this; }

Non_copyable() {}
Non_copyable( Rvref_<Non_copyable> ) {}
};

class Non_movable
{
private:
Non_movable( Rvref_<Non_movable> ) = delete;
auto operator=( Rvref_<Non_movable> ) -> Ref_<Non_movable> =
delete;
public:
auto operator=( Ref_<const Non_movable> )
-> Ref_<Non_movable>
{ return *this; }

Non_movable() {}
Non_movable( Ref_<const Non_movable> ) {}
};

template< class Func >
inline auto invoke( Ref_<const Func> f )
-> decltype( f() )
{ return f(); }

inline auto hopefully( const bool condition )
-> bool
{ return condition; }

inline auto fail( Ref_<const std::string> s )
-> bool
{ throw runtime_error( s ); }

struct Sys_text
{
using Char = wchar_t;
using String = std::wstring;
};

using Char = Sys_text::Char;
using String = Sys_text::String;

# ifndef CPPX_NO_IOSTREAMS
struct Sys
: Sys_text
{
using In_stream = std::wistream;
using Out_stream = std::wostream;

Ref_<In_stream> in = std::wcin;
Ref_<Out_stream> out = std::wcout;
Ref_<Out_stream> err = std::wcerr;
};

const Sys sys{};

inline auto line_from( Ref_<Sys::In_stream> stream )
-> String
{
String result;
getline( stream, result )
|| fail( "cppx::line_from: std::getline failed" );
return result;
}
# endif

inline void trace( Ref_<const Sys::String> s )
{
# if !(defined( CPPX_NO_IOSTREAMS ) || defined( NDEBUG ))
sys.err << "(trace) " << s << endl;
# endif
}

inline auto length( Ref_<const String> s )
-> Size
{ return s.length(); }

inline auto string_from( const wchar_t ch )
-> String
{ return std::wstring( 1, ch ); }

inline auto string_from( Ptr_<const wchar_t> s )
-> String
{ return s; }

template< class Type >
inline auto string_from( Ref_<const Type> value )
-> String
{ return std::to_wstring( value ); }

inline auto operator*( const int n, Ref_<const String> s )
-> String
{
assert( n >= 0 );
switch( length( s ) )
{
case 0: return L"";
case 1: return String( n, s.front() );
}
String result;
for( int i = 1; i <= n; ++i ) { result += s; }
return result;
}

template< class Type >
inline auto operator<<( Ref_<String> s, Ref_<const Type> value )
-> Ref_<String>
{
s += string_from( value );
return s;
}

template< class Type >
inline auto operator<<( Rvref_<String> s, Ref_<const Type> value )
-> Rvref_<String>
{
s += string_from( value );
return move( s );
}

inline auto uppercase( const wchar_t c )
-> wchar_t
{ return towupper( c ); }

inline auto uppercase( Ref_<const String> s )
-> String
{
String result;
for( const wchar_t ch : s )
{
result += uppercase( ch );
}
return result;
}

inline auto mainfunc( Ref_<void()> cpp_mainfunc )
-> int
{
setlocale( LC_ALL, "" ); // User's default locale.
using namespace std;
try
{
cpp_mainfunc();
return EXIT_SUCCESS;
}
catch( exception const& x )
{
// Can be abstracted as general exception handling function.
# ifndef CPPX_NO_IOSTREAMS
sys.err << "!" << x.what() << endl;
# else
(void) x; throw;
# endif
}
return EXIT_FAILURE;
}
} // namespace cppx
</file>

Alf P. Steinbach

unread,
May 13, 2016, 10:57:54 PM5/13/16
to
On 14.05.2016 03:21, Stefan Ram wrote:
> "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
> Subject: Request for comments about this console window interface
>
>
>> // This is just a best effort to restore the console on
>> program termination.

Oh, I posted the implementation code by mistake. Sorry. I tried to
delete it in Thunderbird, but apparently no cancel-message was sent.

I have posted the code I intended to post in a message with roughly the
same title.


> When someone uses comments starting with »//«, one should be
> able to control the line wrapping. Otherwise, I recommend
> comments that start with »/*«.

Yup, you need a better Usenet client. Looks like you're using a DIY one.

In particular this,

<quote>
Archive: no
X-No-Archive-Readme: "X-No-Archive" is only set, because this prevents some
services to mirror the article via the web (HTTP). But Stefan Ram
hereby allows to keep this article within a Usenet archive server
with only NNTP access without any time limitation.
</quote>

is ungood because servers don't read comments... ;-)


Cheers!,

- Alf

Alf P. Steinbach

unread,
May 15, 2016, 10:32:07 AM5/15/16
to
On 15.05.2016 16:12, Stefan Ram wrote:
>
> Transcript of command-line session
>
> 00 < telnet news 119 | tee tmp.txt
> 01 -
> 02 > Connected to news.
> 03 > Escape character is '^]'.
> 04 -
> 05 < article <nh5sah$aer$1...@dont-email.me>
> 06 -
> 10 < quit
> 11 > Connection closed by foreign host.
> 12 -
> 13 < od -c tmp.txt
> 14 -
> 15 > 0007366 { \n
> 16 > 0007400 / /
> 17 > 0007412 T h i s i s j u
> 18 > 0007424 s t a b e s t
> 19 > 0007436 e f f o r t t o
> 20 > 0007450 r e s t o r e t h
> 21 > 0007462 e c o n s o l e
> 22 > 0007474 o n \n p r o g r a
> 23 > 0007506 m t e r m i n a t
> 24 > 0007520 i o n . \n
> 25 > 0007532 / /
>
> One can see in line 22 that there is a spurious »\n«
> that my news server seems to have inserted into your
> post.

That's called “flowed format”: a space before the line ending signifies
a logical line continuation when you have

Content-Type: text/plain; charset=windows-1252; format=flowed

It's just one of the dangers of a DIY news client (assuming that's what
you're using), that there's no team to keep it up with the evolving de
facto standards.

Not sure of which RFC or whatever, sorry. I did know about 10 years ago
or so.


Cheers & hth. :),

- Alf

Alf P. Steinbach

unread,
May 15, 2016, 11:07:25 AM5/15/16
to
On 15.05.2016 17:04, Stefan Ram wrote:
>
> As I already wrote before: The lesson is:
>
> When writing C++ source code, prefer comments that start
> with »/*« because they are more robust should the program
> ever be transferred across a medium where spaces and
> line-break characters might be exchanged.

lols. :)


Cheers!,

- Alf

asetof...@gmail.com

unread,
May 16, 2016, 4:21:02 PM5/16/16
to
For me there is too much complexity
For me all is already easy
with assembly + C + classes from
Compilers 1990
0 new messages