FringePioneer / Cursango

A Curses based terminal client for Chatango.
GNU General Public License v3.0
3 stars 0 forks source link

Resizing [Have fix here...] #1

Open SpeakerfortheExiled opened 12 years ago

SpeakerfortheExiled commented 12 years ago

On resize, ncurses' getch() functions are passed character 410, and your client crashes because that is not a valid ASCII character. I propose you have some sort of wrapper function for getch() to catch all of these and related special characters, because it's just not appealing to see a conditional for this on each and every place you need to get a character, especially annoying if you need expansion.

So, I'd just replace all instances of getch() with getchar() or whateveryouwant() and then define getchar() (or whateveryouwant() ) at the top as something similar to what follows: char getchar() { char d=getch(); if(d==410) { _onResize(); return NULL; } //elifs for other special chars return d; }

FringePioneer commented 12 years ago

Well, after looking in the Python 3 API for its curses module, it seems that it has a KEY_RESIZE constant. If this is the same as character code 410, I presume I can just use that instead of the magic number 410.

Your suggestion of wrapping the curses getch function with something more fine-tuned seems reasonable. I'll make the changes and push a new commit soon.

SpeakerfortheExiled commented 12 years ago

Ah, thanks for letting me know. Fabulous.

FringePioneer commented 12 years ago

Well, I looked into resizing with some simple test curses implementation. Without your fix, any time I call getch, I'm either in Command Mode directly, inputting characters to give an argument to a command (e.g. typing in the room name after initiating the "Join Room" command), or chatting.

When in Command Mode directly, the integer of the key pressed is stored in key and tested through the keyMatches helper function listed below as the Command Mode loop tries to match to a recognized command key.

def keyMatches(key, char):
    if key in range(ord('a'), ord('z')):
        return (key == ord(char)) or (key == ord(char.upper()))
    return (key == ord(char))

Here, nothing special is done with KEY_RESIZE, whose value is hypothetically 410. At no point is it cast to chr.

If none of the command-bound keys are matched in Command Mode directly (i.e. for some character in ['a', 'b', 'c', 'd', 'e', 'h', 'j', 'l', 'm', 'p', 'q', 'u', 'x', '?']), then key is overwritten with a new call to getch and the loop is repeated until one of the expected characters is matched.

When a command is matched, some commands require further input: as of the time of this comment, these commands are announce, ban, delete, join, demote, promote, and unban. In all of these, input is taken by calling escEdit and assigning the returned string to the local "message" variable (usually msg) in each of those commands.

def escEdit(escInt = 27, entInt = 10, bakInt = 127):
    result = ""
    key = inField.getch()

    while not (key == escInt):
        if key == entInt:
            break
        elif key == bakInt:
            if not (result == ""):
                result = result[:-1]
        else:
            result += chr(key)
    else:
        result = ""

    drawInput()

    return result

Here, if a character does not correspond to [Esc], [Enter], or [Backspace], then the key is cast to a chr and appended to result. Here's the interesting part: chr(410) successfully returns a character in my tests. It is a valid character, and bash is fully capable of printing whatever it thinks that grapheme is. Indeed, if you start out Cursango in a smaller window, press the "j" key, and enlarge the window, you should see the character printed successfully while the command is waiting for a room name. The same for enlarging the window while in chat mode, in which case the character will become part of your in-progress chat message.

In Chat Mode, a similar but slight modification of the logic in escEdit is used, listening to [Tab], [Esc], [Enter], and [Backspace]. Again, should a character code received not match any of those keys, the integer is cast to a character by calling chr, and again this is successful when you start with a smaller window and then enlarge the window.


So, I induced and deduced all this while looking through my code, through actual use of Cursango recently, and through use of a test curses application. One thing that the test revealed is that the values in the tuple returned by getmaxyx before and after resizing the window are different. This is revealed both through enlarging the window and through shrinking the window. I suspect that the problem is that an attempt to print a string at a given location on the window after the window is resized such that where the string would have printed no longer exists causes the crash.

As such, I am no longer of the opinion that your proposed fix will actually solve the bug. What needs to be done is something more drastic: actually resizing each of the abstract curses windows and then redrawing everything if the KEY_RESIZE code is received and the tuples returned before and after the resize event differ.

SpeakerfortheExiled commented 12 years ago

Obviously it wouldn't've been as simple as I had demonstrated, I figured my on resize function entailed a bit more than explicitly stated. Ah well, that is not the matter, the matter is the problem is having a solution drawn to it, and that was the point. I'm glad whatever solution you have found works (and is probably several orders of magnitude better than what I could have come up with.)

On Sat, Jun 23, 2012 at 8:32 PM, FringePioneer < reply@reply.github.com

wrote:

Well, I looked into resizing with some simple test curses implementation. Without your fix, any time I call getch, I'm either in Command Mode directly, inputting characters to give an argument to a command (e.g. typing in the room name after initiating the "Join Room" command), or chatting.

When in Command Mode directly, the integer of the key pressed is stored in key and tested through the keyMatches helper function listed below as the Command Mode loop tries to match to a recognized command key.

def keyMatches(key, char):
   if key in range(ord('a'), ord('z')):
       return (key == ord(char)) or (key == ord(char.upper()))
   return (key == ord(char))

Here, nothing special is done with KEY_RESIZE, whose value is hypothetically 410. At no point is it cast to chr.

If none of the command-bound keys are matched in Command Mode directly (i.e. for some character in ['a', 'b', 'c', 'd', 'e', 'h', 'j', 'l', 'm', 'p', 'q', 'u', 'x', '?']), then key is overwritten with a new call to getch and the loop is repeated until one of the expected characters is matched.

When a command is matched, some commands require further input: as of the time of this comment, these commands are announce, ban, delete, join, demote, promote, and unban. In all of these, input is taken by calling escEdit and assigning the returned string to the local "message" variable (usually msg) in each of those commands.

def escEdit(escInt = 27, entInt = 10, bakInt = 127):
   result = ""
   key = inField.getch()

   while not (key == escInt):
       if key == entInt:
           break
       elif key == bakInt:
           if not (result == ""):
               result = result[:-1]
       else:
           result += chr(key)
   else:
       result = ""

   drawInput()

   return result

Here, if a character does not correspond to [Esc], [Enter], or [Backspace], then the key is cast to a chr and appended to result. Here's the interesting part: chr(410) successfully returns a character in my tests. It is a valid character, and bash is fully capable of printing whatever it thinks that grapheme is. Indeed, if you start out Cursango in a smaller window, press the "j" key, and enlarge the window, you should see the character printed successfully while the command is waiting for a room name. The same for enlarging the window while in chat mode, in which case the character will become part of your in-progress chat message.

In Chat Mode, a similar but slight modification of the logic in escEdit is used, listening to [Tab], [Esc], [Enter], and [Backspace]. Again, should a character code received not match any of those keys, the integer is cast to a character by calling chr, and again this is successful when you start with a smaller window and then enlarge the window.


So, I induced and deduced all this while looking through my code, through actual use of Cursango recently, and through use of a test curses application. One thing that the test revealed is that the values in the tuple returned by getmaxyx before and after resizing the window are different. This is revealed both through enlarging the window and through shrinking the window. I suspect that the problem is that an attempt to print a string at a given location on the window after the window is resized such that where the string would have printed no longer exists causes the crash.

As such, I am no longer of the opinion that your proposed fix will actually solve the bug. What needs to be done is something more drastic: actually resizing each of the abstract curses windows and then redrawing everything if the KEY_RESIZE code is received and the tuples returned before and after the resize event differ.


Reply to this email directly or view it on GitHub: https://github.com/FringePioneer/Cursango/issues/1#issuecomment-6529733

There's no hills in flatland.