Skip to content

Some big changes for a potential Version 4 (windows support and better testing) #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 23 commits into from

Conversation

Cube707
Copy link
Collaborator

@Cube707 Cube707 commented Jan 26, 2022

Hello there,
I really like your project ant wanted to contribute. This is more of a Disskussion PR that should probaply target a development branch, but since I can't create new branches here we go.

addidtions

I cleand up the windows side of the code and made it work consistently with the behavior on the linux side. For that I also had to chage a few small things in the Linux functions.

I than went ahead and upgraded the testing system. For the windows side I creaded a number of testcases covering all printable character and all currently supported special keys (Arrow-keys, F-keys, etc.).

On the Linux side I hit a wall and can only get the tests to work localy, not on GitHub. Maybe someone else can fix that?

I also cleand out the repo, removing (hopefully) not needed files and improving the makefile.

drawbacks

The Linuxside ist still mostly untested and not automated. While I copyed most of the code from the current version, I am not that versed with Linux.

Because windows uses some scancodes that can't be utf-8 encoded directly, I have to do the byte->str conversion manually. This however uses function not present in python 2.7, so I doped that for now. It is probably possible to make this work with 2.7 as well, but I didn't want to waste time on it until I have feedback if this is desirable.

While the libary should be mostly compatible with the current version, there are a few bigger changes that my break compatibilyty with some older projects.

@magmax
Copy link
Owner

magmax commented Feb 4, 2022

Hi there!

I like the approach and the idea to launch a version 4.

setup.cfg Outdated
norecursedirs = .git venv build dist *egg
addopts = -rfEsxwX --cov readchar
testpaths = tests
addopts = -rfEsxwX -s --cov=readchar
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
addopts = -rfEsxwX -s --cov=readchar
addopts = -rfEsxwXs --cov=readchar

Copy link
Collaborator Author

@Cube707 Cube707 Feb 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the characters following -r are options for this flag. See the help for pytest:

-r chars - show extra test summary info as specified by chars: (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. (w)arnings are enabled by default (see --disable-warnings), 'N' can be used to reset the list. (default: 'fE').

Thats why they need to be kept seperate.

Maybe this would make it more clear?:

Suggested change
addopts = -rfEsxwX -s --cov=readchar
addopts = -r fEsxwX -s --cov=readchar

@magmax
Copy link
Owner

magmax commented Feb 4, 2022

Please, rebase master. I've updated some of the tooling and workflows.

@Cube707
Copy link
Collaborator Author

Cube707 commented Feb 6, 2022

I will rebase and add your suggstions and update the PR as soon as possible. Thanks for taking a look at it

@Cube707 Cube707 closed this Feb 6, 2022
@Cube707 Cube707 reopened this Feb 6, 2022
@Cube707
Copy link
Collaborator Author

Cube707 commented Feb 7, 2022

@magmax I updated the PR. Take a look when you have the time.

@Theproccy
Copy link

Through brief testing, the changes made do resolve the issues I was experiencing with version 3.0.5.

I second that this should be Pulled into Main and bundled with a new release.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

To fix the linux tests you can use this conftest.py:

import pytest
import sys
import select
import termios
import tty
import readchar.read_linux as read_linux

# ignore all tests in this folder if not on linux
def pytest_ignore_collect(path, config):
    if not sys.platform.startswith("linux"):
        return True


@pytest.fixture
def patched_stdin():
    class mocked_stdin:
        buffer = []

        def push(self, string):
            for c in string:
                self.buffer.append(c)

        def read(self, n):
            string = ""
            for i in range(n):
                string += self.buffer.pop(0)
            return string

    def mock_tcgetattr(fd):
        return None

    def mock_tcsetattr(fd, TCSADRAIN, old_settings):
        return None
        
    def mock_setraw(fd):
        return None
        
    def mock_kbhit():
        return True

    mock = mocked_stdin()
    with pytest.MonkeyPatch.context() as mp:
        mp.setattr(sys.stdin, "read", mock.read)
        mp.setattr(termios, "tcgetattr", mock_tcgetattr)
        mp.setattr(termios, "tcsetattr", mock_tcsetattr)
        mp.setattr(tty, "setraw", mock_setraw)
        mp.setattr(read_linux, "kbhit", mock_kbhit)
        yield mock

We have only 3 failing tests then (I removed all skips)

FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[2~-\x1b[2~] - AssertionError: assert '\x1b[2~' == '\x1b[2'
FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[5~-\x1b[5~] - AssertionError: assert '\x1b[5~' == '\x1b[5'
FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[6~-\x1b[6~] - AssertionError: assert '\x1b[6~' == '\x1b[6'

grafik

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

Ah all the test_functionKeys fail too:

❯ pytest
============================================================================================================= test session starts ==============================================================================================================
platform linux -- Python 3.10.4, pytest-7.1.1, pluggy-1.0.0
rootdir: /home/daniel/Desktop/other_repos/python-readchar, configfile: setup.cfg, testpaths: tests
plugins: cov-3.0.0, Faker-12.1.0, typeguard-2.13.3
collected 134 items

tests/linux/test_keys.py ....
tests/linux/test_readchar.py ...........................................................................................................
tests/linux/test_readkey.py .....F...FFFFFFFFFFFFFF

=================================================================================================================== FAILURES ===================================================================================================================
______________________________________________________________________________________________________ test_specialKeys[\x1b[2~-\x1b[2~] _______________________________________________________________________________________________________

seq = '\x1b[2~', key = '\x1b[2~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f77280>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            ("\x1b\x5b\x32\x7e", key.INSERT),
            ("\x1b\x5b\x33\x7e", key.SUPR),
            ("\x1b\x5b\x48", key.HOME),
            ("\x1b\x5b\x46", key.END),
            ("\x1b\x5b\x35\x7e", key.PAGE_UP),
            ("\x1b\x5b\x36\x7e", key.PAGE_DOWN),
        ],
    )
    def test_specialKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1b[2~' == '\x1b[2'
E         -
E         +
E         ?    +

tests/linux/test_readkey.py:38: AssertionError
______________________________________________________________________________________________________ test_specialKeys[\x1b[5~-\x1b[5~] _______________________________________________________________________________________________________

seq = '\x1b[5~', key = '\x1b[5~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f5faf0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            ("\x1b\x5b\x32\x7e", key.INSERT),
            ("\x1b\x5b\x33\x7e", key.SUPR),
            ("\x1b\x5b\x48", key.HOME),
            ("\x1b\x5b\x46", key.END),
            ("\x1b\x5b\x35\x7e", key.PAGE_UP),
            ("\x1b\x5b\x36\x7e", key.PAGE_DOWN),
        ],
    )
    def test_specialKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1b[5~' == '\x1b[5'
E         -
E         +
E         ?    +

tests/linux/test_readkey.py:38: AssertionError
______________________________________________________________________________________________________ test_specialKeys[\x1b[6~-\x1b[6~] _______________________________________________________________________________________________________

seq = '\x1b[6~', key = '\x1b[6~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f76d40>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            ("\x1b\x5b\x32\x7e", key.INSERT),
            ("\x1b\x5b\x33\x7e", key.SUPR),
            ("\x1b\x5b\x48", key.HOME),
            ("\x1b\x5b\x46", key.END),
            ("\x1b\x5b\x35\x7e", key.PAGE_UP),
            ("\x1b\x5b\x36\x7e", key.PAGE_DOWN),
        ],
    )
    def test_specialKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1b[6~' == '\x1b[6'
E         -
E         +
E         ?    +

tests/linux/test_readkey.py:38: AssertionError
_______________________________________________________________________________________________________ test_functionKeys[\x1bOP-\x1bOP] _______________________________________________________________________________________________________

seq = '\x1bOP', key = '\x1bOP', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f5fc70>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bOP' == '\x1bO'
E         -
E         + P
E         ?   +

tests/linux/test_readkey.py:60: AssertionError
_______________________________________________________________________________________________________ test_functionKeys[\x1bOQ-\x1bOQ] _______________________________________________________________________________________________________

seq = '\x1bOQ', key = '\x1bOQ', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f768c0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bOQ' == '\x1bO'
E         -
E         + Q
E         ?   +

tests/linux/test_readkey.py:60: AssertionError
_______________________________________________________________________________________________________ test_functionKeys[\x1bOR-\x1bOR] _______________________________________________________________________________________________________

seq = '\x1bOR', key = '\x1bOR', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f764a0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bOR' == '\x1bO'
E         -
E         + R
E         ?   +

tests/linux/test_readkey.py:60: AssertionError
_______________________________________________________________________________________________________ test_functionKeys[\x1bOS-\x1bOS] _______________________________________________________________________________________________________

seq = '\x1bOS', key = '\x1bOS', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f767d0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bOS' == '\x1bO'
E         -
E         + S
E         ?   +

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO15~-\x1bO15~] _____________________________________________________________________________________________________

seq = '\x1bO15~', key = '\x1bO15~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f74310>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO15~' == '\x1bO'
E         -
E         + 15~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO17~-\x1bO17~] _____________________________________________________________________________________________________

seq = '\x1bO17~', key = '\x1bO17~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f750f0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO17~' == '\x1bO'
E         -
E         + 17~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO18~-\x1bO18~] _____________________________________________________________________________________________________

seq = '\x1bO18~', key = '\x1bO18~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f76590>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO18~' == '\x1bO'
E         -
E         + 18~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO19~-\x1bO19~] _____________________________________________________________________________________________________

seq = '\x1bO19~', key = '\x1bO19~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f779d0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO19~' == '\x1bO'
E         -
E         + 19~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO20~-\x1bO20~] _____________________________________________________________________________________________________

seq = '\x1bO20~', key = '\x1bO20~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f775e0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO20~' == '\x1bO'
E         -
E         + 20~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO21~-\x1bO21~] _____________________________________________________________________________________________________

seq = '\x1bO21~', key = '\x1bO21~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f74100>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO21~' == '\x1bO'
E         -
E         + 21~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO23~-\x1bO23~] _____________________________________________________________________________________________________

seq = '\x1bO23~', key = '\x1bO23~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f74d00>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO23~' == '\x1bO'
E         -
E         + 23~

tests/linux/test_readkey.py:60: AssertionError
_____________________________________________________________________________________________________ test_functionKeys[\x1bO24~-\x1bO24~] _____________________________________________________________________________________________________

seq = '\x1bO24~', key = '\x1bO24~', patched_stdin = <conftest.patched_stdin.<locals>.mocked_stdin object at 0x7fa779f76ad0>

    @pytest.mark.parametrize(
        ["seq", "key"],
        [
            (key.F1, "\x1b\x4f\x50"),
            (key.F2, "\x1b\x4f\x51"),
            (key.F3, "\x1b\x4f\x52"),
            (key.F4, "\x1b\x4f\x53"),
            (key.F5, "\x1b\x4f\x31\x35\x7e"),
            (key.F6, "\x1b\x4f\x31\x37\x7e"),
            (key.F7, "\x1b\x4f\x31\x38\x7e"),
            (key.F8, "\x1b\x4f\x31\x39\x7e"),
            (key.F9, "\x1b\x4f\x32\x30\x7e"),
            (key.F10, "\x1b\x4f\x32\x31\x7e"),
            (key.F11, "\x1b\x4f\x32\x33\x7e"),
            (key.F12, "\x1b\x4f\x32\x34\x7e"),
        ],
    )
    def test_functionKeys(seq, key, patched_stdin):
        patched_stdin.push(seq)
>       assert key == readkey()
E       AssertionError: assert '\x1bO24~' == '\x1bO'
E         -
E         + 24~

tests/linux/test_readkey.py:60: AssertionError

---------- coverage: platform linux, python 3.10.4-final-0 -----------
Name                       Stmts   Miss  Cover
----------------------------------------------
readchar/__init__.py           9      4    56%
readchar/key_linux.py         60      0   100%
readchar/key_windows.py       31     31     0%
readchar/read_linux.py        30      3    90%
readchar/read_windows.py      13     13     0%
----------------------------------------------
TOTAL                        143     51    64%

=========================================================================================================== short test summary info ============================================================================================================
FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[2~-\x1b[2~] - AssertionError: assert '\x1b[2~' == '\x1b[2'
FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[5~-\x1b[5~] - AssertionError: assert '\x1b[5~' == '\x1b[5'
FAILED tests/linux/test_readkey.py::test_specialKeys[\x1b[6~-\x1b[6~] - AssertionError: assert '\x1b[6~' == '\x1b[6'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bOP-\x1bOP] - AssertionError: assert '\x1bOP' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bOQ-\x1bOQ] - AssertionError: assert '\x1bOQ' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bOR-\x1bOR] - AssertionError: assert '\x1bOR' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bOS-\x1bOS] - AssertionError: assert '\x1bOS' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO15~-\x1bO15~] - AssertionError: assert '\x1bO15~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO17~-\x1bO17~] - AssertionError: assert '\x1bO17~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO18~-\x1bO18~] - AssertionError: assert '\x1bO18~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO19~-\x1bO19~] - AssertionError: assert '\x1bO19~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO20~-\x1bO20~] - AssertionError: assert '\x1bO20~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO21~-\x1bO21~] - AssertionError: assert '\x1bO21~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO23~-\x1bO23~] - AssertionError: assert '\x1bO23~' == '\x1bO'
FAILED tests/linux/test_readkey.py::test_functionKeys[\x1bO24~-\x1bO24~] - AssertionError: assert '\x1bO24~' == '\x1bO'
======================================================================================================== 15 failed, 119 passed in 1.00s ========================================================================================================

@Cube707
Copy link
Collaborator Author

Cube707 commented Apr 27, 2022

very good. I will incorperate this into this PR in the next few days.

Most of the Linux code I just copied over from the current version and tried my best to make it a little more dynamic (while having mostly no idea if it is acually a good aproach). So if you have suggestions on how to make this better, feel free to leave them here as well.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

this read_linux.py fixes all tests: I changed line 44, 48, 52 to scan longer scan codes and added 55-57 so we can scan the codes for F5-F10 (they have 5 parts)

# -*- coding: utf-8 -*-
import sys
import termios
import tty
import select


# idea from:
# https://repolinux.wordpress.com/2012/10/09/non-blocking-read-from-stdin-in-python/
# Thanks to REPOLINUX
def kbhit():
    return sys.stdin in select.select([sys.stdin], [], [], 0)[0]


# Initially taken from:
# http://code.activestate.com/recipes/134892/
# Thanks to Danny Yoo
def readchar(blocking=False):
    if not blocking and not kbhit():
        return None
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch


def readkey():
    """Get a single character on Linux. If an escaped key is pressed, the
    following characters are read as well (see key_linux.py)."""

    c1 = readchar(blocking=True)

    if c1 == "\x03":
        raise KeyboardInterrupt

    if c1 != "\x1B":
        return c1

    c2 = readchar(blocking=True)
    if c2 not in ["\x4F", "\x5B"]:
        return c1 + c2

    c3 = readchar(blocking=True)
    if c3 not in ["\x31", "\x32", "\x33", "\x35", "\x36"]:
        return c1 + c2 + c3

    c4 = readchar(blocking=True)
    if c2 != "\x4F" or c4 not in ["\x30", "\x31", "\x33", "\x34", "\x35", "\x37", "\x38", "\x39"]:
        return c1 + c2 + c3 + c4

    c5 = readchar(blocking=True)
    return c1 + c2 + c3 + c4 + c5

pytest
================================================ test session starts ================================================
platform linux -- Python 3.10.4, pytest-7.1.1, pluggy-1.0.0
rootdir: /home/daniel/Desktop/other_repos/python-readchar, configfile: setup.cfg, testpaths: tests
plugins: cov-3.0.0, Faker-12.1.0, typeguard-2.13.3
collected 134 items

tests/linux/test_keys.py ....
tests/linux/test_readchar.py ...........................................................................................................
tests/linux/test_readkey.py .......................

---------- coverage: platform linux, python 3.10.4-final-0 -----------
Name                       Stmts   Miss  Cover
----------------------------------------------
readchar/__init__.py           9      4    56%
readchar/key_linux.py         60      0   100%
readchar/key_windows.py       31     31     0%
readchar/read_linux.py        33      4    88%
readchar/read_windows.py      13     13     0%
----------------------------------------------
TOTAL                        146     52    64%


================================================ 134 passed in 0.53s ================================================

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

I also tested the codes via manual input with a short script :D they work now ^^ It's funny that they never worked before :DD but I also only used the Arrow keys and Enter/Space Keys till now.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

Backspace is on linux 0x7f see http://www.macfreek.nl/memory/Backspace_and_Delete_key_reversed
this can also be chagned

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 27, 2022

CTRL_A to CTRL_Z codes are the same on windows as in linux.

@Cube707
Copy link
Collaborator Author

Cube707 commented Apr 27, 2022

That sounds very good. If you want to you can open a PR on my fork and I can push to this PR from there. That we can also keep diskussing there and stop kluttering this PR :)

EDIT: I jsut created a branch on my fork for development, use that because everything I put on master gets directly pushed to this PR

@C0D3D3V
Copy link
Contributor

C0D3D3V commented Apr 28, 2022

in __init__.py __all__ should be a list of strings
__all__ = ["readchar", "readkey", "key"]

@Cube707
Copy link
Collaborator Author

Cube707 commented Apr 28, 2022

I think you did the work on this and so it is only fair that you are listed as the commits author :)
But if you don't care I will incorperate the changes in the comming days.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 3, 2022

You should add the check for darwin
elif sys.platform == "darwin":
macos should also use the linux readchar

@Cube707
Copy link
Collaborator Author

Cube707 commented May 3, 2022

good idea and it all the tests seam to pass: https://github.com/Cube707/python-readchar/runs/6272490957?check_suite_focus=true

But I would also like some to test it manually a little. Do you have a Mac? I don't and I would like to not have to set up a VM for that...

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 3, 2022

Nope I also have no Mac :D
I only became aware of it because one of my users complained: C0D3D3V/Moodle-DL#143
but i'm still waiting to see if it works for him :D though i only use a small subset of the keys in my project.

@Cube707
Copy link
Collaborator Author

Cube707 commented May 3, 2022

I just realised that my flatmate has a Mac. I will ask him and test it a bit later.

@Cube707
Copy link
Collaborator Author

Cube707 commented May 3, 2022

Notes for Mac:

Most stuff seems to work fine, espacially the arrow keys work.

But I found a few keys that don't work:

  • ENTER is \r
  • F5 \x1b[15~
  • F6 \x1b[17~
  • F7 \x1b[18~
  • F10 \x1b[21~
  • F12 \x1b[24~

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 4, 2022

import readchar

want_exit = True
while True:
    readed_key = readchar.readkey()
    found_key_name = None
    for known_key_name in vars(readchar.key):
        known_key = getattr(readchar.key, known_key_name)
        if known_key == readed_key:
            found_key_name = known_key_name
            break
    hex_rep = ":".join("{:02x}".format(c) for c in readed_key.encode())
    if found_key_name is None:
        print(f"Key not in Keylist: '{readed_key}'")
        print(f"In Hex: {hex_rep}")
    else:
        print(f"You pressed: {found_key_name}")
        print(f"In Hex: {hex_rep}")

    if readed_key == readchar.key.CTRL_C and want_exit:
        exit(0)
    elif readed_key == readchar.key.CTRL_C and not want_exit:
        want_exit = True
        print(f"Press {found_key_name} again to exit")
    elif want_exit:
        want_exit = False

I made this little script for testing. We could maybe ship a clean version of this for manual testing.

F5 to F12 also do not work on linux, but Enter works

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 4, 2022

Also Ctr+C raises a KeyboardInterrupt, the old version did not.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 4, 2022

The Hex codes for f5 to f12 are wrong, they should be:

F5 = "\x1b\x5b\x31\x35\x7e"
F6 = "\x1b\x5b\x31\x37\x7e"
F7 = "\x1b\x5b\x31\x38\x7e"
F8 = "\x1b\x5b\x31\x39\x7e"
F9 = "\x1b\x5b\x32\x30\x7e"
F10 = "\x1b\x5b\x32\x31\x7e"
F11 = "\x1b\x5b\x32\x33\x7e"
F12 = "\x1b\x5b\x32\x34\x7e"

then you also have to change line 52 in read_linux

  if c4 not in "\x30\x31\x33\x34\x35\x37\x38\x39":

I just tested it:

python ./manual.py
You pressed: F5
In Hex: 1b:5b:31:35:7e
You pressed: F6
In Hex: 1b:5b:31:37:7e
You pressed: F7
In Hex: 1b:5b:31:38:7e
You pressed: F8
In Hex: 1b:5b:31:39:7e
You pressed: F9
In Hex: 1b:5b:32:30:7e
You pressed: F10
In Hex: 1b:5b:32:31:7e
You pressed: F11
In Hex: 1b:5b:32:33:7e
You pressed: F12
In Hex: 1b:5b:32:34:7e

I can make a PR to your master xD

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 4, 2022

On linux it depends on what Enter you press, the Numpad Enter is LF, the normal Enter is CR

We probably should also remove ESCAPE_SEQUENCES, I have no idea what use that has. Since they overlap with the Function keys, I think they are wrong

@Cube707
Copy link
Collaborator Author

Cube707 commented May 4, 2022

I also figured that the codes for f5... are probably wrong, but my Linux VM decided to explode yesterday, so I didn't get to testing.

I don't have time today and also probably not tomorrow, so if you hsve the timd go ahead and make a PR.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 4, 2022

already done

@Cube707
Copy link
Collaborator Author

Cube707 commented May 5, 2022

I made this little script for testing. We could maybe ship a clean version of this for manual testing.

I actually removed the testing script, but by now I am considering putting it back. I will at least put it on the dev branch...

Also Ctr+C raises a KeyboardInterrupt, the old version did not.

That is also deliberate. I feel it is more pythonic that way. When using something like:

while True:
    c = readkey()
    if c == key.UP:
        break

You have to make sure the user gets not trapped and having to catche the CTRL_C manually feels unnececary and will be forgotten often. (Also only readkey raises the error, readchar still returnes the CTRL_C as is).

But this is of course open for debate and was just what I felt made the most sense.

On linux it depends on what Enter you press, the Numpad Enter is LF, the normal Enter is CR

interesting and anoying, We will have to wait for userfeedback to see if anyone has problems with that...

We probably should also remove ESCAPE_SEQUENCES, I have no idea what use that has. Since they overlap with the Function keys, I think they are wrong.

I guesse this was an attemped to simplify the readingprocess by looping and comparing against teh list od escape_secenses to now how many bytes to read. But it is no longer needed an I will remove it.

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 6, 2022

I actually removed the testing script, but by now I am considering putting it back. I will at least put it on the dev branch...

what was there already a test script xD then I would not have had to make the effort ^^ I would find it handy if you would add something like this in the test folder, you can also quite quickly determine the keycodes for combinations that we do not have in the list, such as Alt+G so all Alt combinations.

@Cube707
Copy link
Collaborator Author

Cube707 commented May 6, 2022

for now I added it under : tests/manual-test.py

for final decitions we will have to wait for @magmax anyway...

I will also reorder and compress the commits once I get some feedback from him. Then I will probably not delete the testing script and jut update it a little

@Cube707
Copy link
Collaborator Author

Cube707 commented May 6, 2022

btw: I have been thinking about the filestructure and the importprocess a little. Whats your opinion on this:

import readchar.key is not longer possible, only from readchar import key. Should I re-add support for that, even if its a little hacky?

when typing import readchar.read... or from readchar import read... my autocompletion will suggest the read_windows/linux subfiles. This is anoying and potentioally confusing. My first idea was to prefix the files with an underscoore (to mark them as internal), this puts them at the botom of the sugestions, but they still come up. Second idea was to change the naminsceam to platform first, than function, so renaming read_linux.py->linux_read.py avoiding the similar names and therefore the problem. Maybe even implement both at the same time?

This than also brings up the question on how to implement mac support. Should it get its own files? even if it just reuses pretty much all of the linux code? Or would be a seperate if line in the init file be clear enough? (maybe also rename linux->unix to make it more clear that its generally used)

@C0D3D3V
Copy link
Contributor

C0D3D3V commented May 6, 2022

I find my version of the test script better, since it can output all keys from the key definitions. Maybe you can adapt it according to your preferences.

You could just make an extra file key.py which will include key_linux or key_python depending on the os. I personally don't need it, but then we're backwards compatible.
I would not hide the OS_dependent versions and also not show them further down in the list. Python itself does not do that with OS dependent modules (e.g. PurePosixPath) I find it as suffix also good, but would also be good as prefix.

you can rename linux to posix or unix, i think in python core posix is used more often.

@Cube707
Copy link
Collaborator Author

Cube707 commented May 9, 2022

I find my version of the test script better, since it can output all keys from the key definitions. Maybe you can adapt it according to your preferences.

good idea, I didn't realise you did that. Stolen and incorporated ;)

@Cube707
Copy link
Collaborator Author

Cube707 commented Jul 4, 2022

closing in favor of #79

@Cube707 Cube707 closed this Jul 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants