rang icon indicating copy to clipboard operation
rang copied to clipboard

Question: Willing to Accept More Curses-Like Features?

Open qbradq opened this issue 8 years ago • 20 comments

There is a community of people who still develop applications with the ncurses library. Most of these applications do not require the full feature set of ncurses, only require a few basic features. Would you be willing to accept pull requests for the below feature set or do you feel they fall outside the scope of Rang?

// Hide the cursor
std::cout << rang::control::hideCursor;

// Clear the screen
std::cout << rang::clear;

// Discover terminal dimensions
int w, h;
rang::dimensions(w, h);

// Position cursor
std::cout << rang::pos((w - 20) / 2, 2) << "How many widgets? " << std::endl;

// Get buffered input
string linebuf;
rang::cin >> linebuf;

// Get unbuffered input
std::cout << rang::pos((w - 20) / 2, 4) << "Press any key to exit" << std::endl;
while(true) {
    rang::key key;
    rang::cin >> key;
    if(key == 'q' || key == 'Q') {
        break;
    }
}

// Convenience method for clearing the terminal and resetting all options to normal
std::cout << rang::control::reset;

FEATURES -

  • Hide cursor

    • [ ] vt100 - http://wiki.bash-hackers.org/scripting/terminalcodes#cursor_handling
    • [ ] windows < 10 - http://stackoverflow.com/a/30126700/2648679
  • Clear the screen

    • [ ] vt100 - https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
    • [ ] windows < 10 - same as above is said to work on win systems too. eg - cout << "\033[2J\033[1;1H"; but needs testing till atleast win7.
  • Discover Terminal Dimensions

    • [ ] vt100
    • [ ] windows < 10
  • Position Cursor

    • [ ] vt100 - http://wiki.bash-hackers.org/scripting/terminalcodes#cursor_handling
    • [ ] windows < 10
  • Get buffered input

    • [ ] vt100
    • [ ] windows < 10
  • Get unbuffered input

    • [ ] vt100
    • [ ] windows < 10
  • Reset Terminal - would be probably a combination of above methods.

    • [ ] vt100
    • [ ] windows < 10

Other links -

  • [x] https://en.wikipedia.org/wiki/ANSI_escape_code
  • [x] http://wiki.bash-hackers.org/scripting/terminalcodes
  • [x] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682073(v=vs.85).aspx
  • [x] https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx

qbradq avatar Dec 17 '16 13:12 qbradq

@qbradq as long as they are in their separate namespace, why not? Those would be great additions to the library and still preserve the minimalism as you say.

My only concern would be their compatibility with sheer number of terminals and of course with windows 10 cmd too. But as long as calling those methods does nothing on non supported terms, we're doing fine. eg calling rang::style::blink does nothing on non-supported terms and no garbage is produced either.

Also since I'm not so experienced with using ncurses lib yet, I can make you a collaborator of this repo too so you can test out code much faster.

agauniyal avatar Dec 17 '16 13:12 agauniyal

I'll give you a warning up front: I haven't coded in C++ in 20 years and I'm only just getting back into things. Can you help me understand what you mean by a separate namespace? Do you mean a separate wrapping namespace like rang_implementation or a separate client-facing namespace like rang? Also I can test things just fine in my fork. Don't worry about that :)

qbradq avatar Dec 17 '16 16:12 qbradq

@qbradq it's much simpler. rang_implementation is for things user don't want to use directly as in with their IDE autocompletion. But other than that, when you'd be introducing a bunch of new methods/objects and if they share some concept, put them in the same namespace eg your example demonstrated - rang::control::hideCursor; and rang::control::reset; both in same control namespace. but if we put same hideCursor inside say rang::fg, it will give some other meaning - rang::fg::hideCursor; which could be confusing.

There is a develop branch against which PRs are opened since I keep master for stable changes. And please do ask if any support from my side is required, I'll be happy to provide it 😄 .

agauniyal avatar Dec 17 '16 18:12 agauniyal

I would be interested in helping with this also. I was looking initially at starting with the pos() code https://msdn.microsoft.com/en-us/library/system.console.setcursorposition(v=vs.110).aspx?f=255&MSPPError=-2147217396&cs-save-lang=1&cs-lang=cpp#code-snippet-1

@qbradq have you started working on this at all?

lefticus avatar Dec 20 '16 03:12 lefticus

I would love these features too - ready to help if there's any need

AxelStrem avatar Dec 20 '16 09:12 AxelStrem

@lefticus I am working on it now. I've been buried at work for the past several days but I finally got that issue resolved. Plus I've got some time off coming :)

Thank you both for offering to help. Feel free to work on what you like. I am starting with positional output. Perhaps one of you could start on the input portion? I was thinking we'd probably want to separate the input handling into a separate header file to keep the compile-time overhead down for those that don't need or want input handling. Thoughts @agauniyal ?

qbradq avatar Dec 21 '16 17:12 qbradq

@qbradq is compile time overhead large enough for this change? As far as I know most people use build systems(or are moving to use one) and most of the project isn't recompiled each time a new change is introduced. Another point is the ease of use this lib provides, just drop the header file and use it. Plus the work this lib does wouldn't be spread across 100s of files that's for sure.

So I would say let's keep it in a single file for some time and test this out 😄 . I'm still open to further feedback though.

EDIT - I've also edited your top comment to track this issue features, hope you don't mind 😄 .

agauniyal avatar Dec 21 '16 18:12 agauniyal

@agauniyal Input handling could get pretty large. There's a lot of variance between terminals that we could run into. And for some use cases like a terminal-only game terminal output can be spread out all over the place. However I do agree that it's better to wait until a problem presents itself before trying to solve it.

qbradq avatar Dec 21 '16 18:12 qbradq

@qbradq if the situation demands, I won't disagree to splitting up input methods into rang-input.hpp.

agauniyal avatar Dec 21 '16 18:12 agauniyal

I realize now that the test program for some of the conditional output will require user interactivity. So I'm going to work on the input side of things first.

qbradq avatar Dec 21 '16 18:12 qbradq

@AxelStrem maybe you can help with 'hide cursor' and 'clear screen' issue since they seem to be well supported in vt100(linux and win >=10) machines but I've no idea with win < 10 versions of cmd. I've linked info in this comment under features - https://github.com/agauniyal/rang/issues/51#issue-196221739

agauniyal avatar Dec 21 '16 18:12 agauniyal

@agauniyal @AxelStrem For windows < 10 see Windows Console Functions. Use FillConsoleOutputAttribute() and FillConsoleOutputCharacter() to clear the screen.

qbradq avatar Dec 22 '16 00:12 qbradq

@agauniyal What do you think of this interface?

rang::Key key;
std::cin
    >> rang::control::blocking
    >> key;
if(key == 'q') { /* Do Stuff */ }
else if (key.isSpecial()) {
    switch(key) {
        case rang::Key::F1: help(); break;
    }
}

std::cin
    >> rang::control::nonBlocking
    >> key;
while(key.isEmpty()) { std::cin >> key; }

/* Same processing as before. */

std::string name;
std::cout >> "What is your name? ";
std::cin << name; // Always blocking
std::cout >> "Hello, " >> name >> ", how are you today?" >> std::endl;

std::cout >> "Press Any Key to Continue";
std::cin
    >> rang::control::flush // Flush any pending input, like the \n from the prompt
    >> rang::control::blocking
    >> key;

Input for Key objects from non-tty's would be disabled.

qbradq avatar Dec 23 '16 02:12 qbradq

@qbradq this looks good to me but again I'm not so experienced with such code. I do have a few ques -

  • In rang::Key::F1, about these special keys, how many of them would be supported? F1-12? or something else too. And how are these stored and recognized? like in an enum, or struct?

  • what is rang::Key basic type here? Or should I frame the ques as, what is it analogous to - a character or a string or something else? I ask this because there are multiple usages of key such as key == 'q' , key.isSpecial(), switch(key), key.isEmpty() and with std::cin too.

  • what does rang::control::nonBlocking/ rang::control::blocking do behind the scenes?

  • How is rang::control::flush different from std::cin's method - clear() & ignore().

  • When should we use unbuffered input vs buffered input? I'm familiar with unbuffered vs buffered output as used with logging/error messages but not so with input.

  • would all functionality mentioned, supported with windows and linux both?

Sorry to bug you with lot of ques, I'll need to update the wiki as well so I'm trying to get a prespective about the additions 😄 .

agauniyal avatar Dec 23 '16 04:12 agauniyal

@agauniyal

  • I'm not so experienced with header-only libraries. So rang::Key::F1 became rang::Key::Which::F1.
    • This is an enum.
    • There will be one entry for every non-printable key on the standard keyboard layout excluding modifier keys like Shift and Alt. Detecting keypresses for these keys is not portable as there is no way to do it on VT100.
  • rang::Key is a class that encapsulates a single 32-bit value (so it is efficient to pass by value).
    • It implements operator int() such that key == 'q' and switch(key) work.
    • It provides reasoning methods such as isSpecial(), isPrintable(), and isPrintableAscii() to help the client understand the key's properties without understanding the implementation.
    • It provides a special operator>>(istream&, Key&) that invokes the unbuffered input mode of the rang library for that one input operation only to reduce the burden of state management on the client.
  • rang::control::blocking and rang::control::nonBlocking set option bools that inform the unbuffered input methods whether or not to block waiting for input.
  • In order to achieve unbuffered input we have to use fgetc on POSIX systems and ReadConsoleInput on Windows < 10. I still don't know how to achieve this on Windows >= 10. Using std::cin.clear() or std::cin.ignore() still operates in the buffered mode. When using termios on POSIX systems to set the terminal to unbuffred and then using the std::cin functions the terminal never returns to buffered mode. I do not know why. Even if this did work the client code can't simply invoke std::cin.clear() / std::cin.ignore().
  • All interactions with std::cin will be buffered except std::cin >> rang::Key. Unbuffered input simply means the input is available before the user strikes the return key.
    • Regarding blocking and non-blocking modes: Blocking means that an invocation of std::cin >> rang::Key will not return until there is valid data to be returned in the Key object. Non-blocking means that this same call will result in the operator always returning immediately. If no input was available then key.isEmpty() == true.
    • A further point, I've had to add an additional option for non-blocking input to avoid CPU saturation. std::cin >> rang::control::normalLatency is the default and will result in a minimum delay of 100ms wall clock time for all non-blocking calls to std::cin >> rang::Key. To eliminate this minimum delay set std::cin >> rang::control::lowLatency.
  • All functionality mentioned except non-printable input is currently working on POSIX systems with a VT100-compatible terminal and on Windows 7. I am just starting work on VT100/POSIX support for non-printable. I will start working on Windows 10 after I finish non-printable for the other platforms.

Please let me know if you'd like to see the PR in progress or would rather wait for the squash.

qbradq avatar Dec 23 '16 13:12 qbradq

@qbradq ReadConsoleInput should work on all version of windows, shouldn't it? We just made a distinction between windows >=10 and windows < 10 because windows 10 finally supported ansi escape sequences in cmd, while earlier versions weren't compatible so we had to use native windows methods. So if an existing function which is working on windows 7, it should work in newer versions too unless specified so.

Otherwise everything looks good and I also saw some of the work you did in your branch which looks promising. I would like to see a PR in progress so that we could transfer some of the discussion about input features there and keep this thread for overall discussion about all the features.

agauniyal avatar Dec 23 '16 15:12 agauniyal

@qbradq you'll be glad to know that terminfo parser has finally landed in develop branch. So stuff like hiding cursor, clearing screen are a single line addition to library now 😄 . Though we still need a terminfo parameterized string expansion method before going further.

agauniyal avatar Mar 12 '17 02:03 agauniyal

😃 I am glad that I found this library and this issue because I am currently developing games on terminals for research. My games need features below and I have written some simple functions to fulfill it:

I am wondering if the last two methods can be added to this library since it seems to fall outside of RANG's scope. Thanks in advance!

chuyangliu avatar Nov 16 '17 02:11 chuyangliu

@chuyangliu yes they can be added to the library! There is an undergoing work with terminfo parser which will bring methods like setCursor() and clear() very soon to all linux terminals and windows already has those functions you've used. The last two functions can be put inside rang::input namespace. There is a new release 2.5 coming in 2-3 days which adds support for windows mingw/cygwin/bash and all sorts of terminals, then there will be another release 3.0 with terminfo support maybe in late November where I'll add all these features. You can request more if you need for help with your research and I'll do my best to add them whenever I get time 😄 .

agauniyal avatar Nov 16 '17 03:11 agauniyal

@agauniyal Thanks a lot. I am looking forward to it!

chuyangliu avatar Nov 16 '17 03:11 chuyangliu