grace.cutler
grace.cutler

Reputation: 345

python key input logic using msvcrt

I need to do roughly the following:

while True:
    if *a key is pressed within 5 seconds of some prior event*:
        print(*the specific key that was pressed)
    elif *5 seconds pass with no key press*
        print("No key pressed")

I posted about my specific needs in another question (Read specific key with msvcrt.getch() OR move on after set time with no input), but I figure this format is much more approachable. The critical part here is that I must know what key was pressed. I have been trying to use msvcrt.getch() amd msvcrt.kbhit(), but it seems I need some sort of hybrid.

Can anyone figure this out?

Windows 11, Python 3.11.0

Upvotes: 1

Views: 61

Answers (1)

R2509
R2509

Reputation: 35

Here's a potential solution, tested on Windows 11 with Python 3.11 and 3.12. It works by checking how long ago the event of interest was fired. See docstrings and comments below:

import msvcrt
import time


class KeyboardChecker:
    """Checks for input."""
    
    def __init__(self):
        self.last_event_trigger_time = None

    def event_triggered(self):
        """Call this when your event is triggered."""
        self.last_event_trigger_time = time.perf_counter()
        print(
            round(self.last_event_trigger_time, 3),
            '[KeyboardChecker]: Event triggered.',
        )

    def _read_all_input(self):
        """Read all available input."""
        input_data = b''
        while msvcrt.kbhit():
            # Read and store input until no more is available.
            input_data += msvcrt.getch()

        return input_data

    def check_input(self):
        """Check if we have received input within 5 seconds of the last event."""
        if self.last_event_trigger_time is None:
            # If the trigger time is still `None`, the
            # event has not been triggered yet.

            # Don't run the next part of the code.
            return None  # This is the same as `return`.

        current_time = time.perf_counter()
        if current_time - self.last_event_trigger_time > 5:
            # If the event was last triggered more than 5
            # seconds ago, don't do anything with the input.

            # OPTIONAL: Read input but don't use it. This
            # means you won't have anything waiting to be
            # read the next time you want to read any
            # input, which may be useful.
            _not_used = self._read_all_input()

            # Don't run the next part of the code.
            return None

        # By the time we reach this point (if at all), we
        # know that the event was triggered and that it was
        # 5 seconds ago or less. We can now read our input,
        # if we have any.
        if msvcrt.kbhit():
            # If we have received input, read it all.
            return self._read_all_input()

        # If we do not have any input, return an empty
        # bytestring to distinguish from the rejection
        # cases above.
        return b''

You can call .event_triggered() whenever you fire your event, and call .check_input() to check that we have input and that it's not too late to get it (based on when the event was last fired).

Here is a usage example...

# Create a `KeyboardChecker` instance so we can do all of
# our checking.
keyboard_checker = KeyboardChecker()

# This gets called when the event is triggered. We will
# call it here for demonstration.
keyboard_checker.event_triggered()

# Now, we run a while loop that loops every half-second
# until calls to `keyboard_checker.check_input()` are
# rejected (when it starts reurning None).
while True:
    # Record the time.
    timestamp = round(time.perf_counter(), 3)

    # Check for input.
    received_input = keyboard_checker.check_input()

    # Handle all cases.
    if received_input is None:
        # Event was triggered more than 5 seconds ago.
        print(timestamp, 'The call to `.check_input()` was rejected! Stopping loop...')
        break  # Stop the loop.

    elif received_input == b'':
        # Input was checked, but none was received.
        print(timestamp, 'No characters received.')

    else:
        # Input was checked and data was received.
        print(timestamp, f'Received the following data: {received_input}.')

    # Wait for half a second.
    time.sleep(0.5)

...and the output it generated when I ran it.

1491860.076 [KeyboardChecker]: Event triggered.
1491860.076 Received the following data: b'f'.
1491860.578 Received the following data: b'o'.
1491861.081 Received the following data: b'o'.
1491861.584 No characters received.
1491862.085 Received the following data: b'b'.
1491862.588 Received the following data: b'a'.
1491863.091 Received the following data: b'r'.
1491863.593 No characters received.
1491864.095 No characters received.
1491864.597 No characters received.
1491865.098 The call to `.check_input()` was rejected! Stopping loop...

Notice that calls to .check_input() are rejected once we pass the 5-second mark.

Upvotes: 0

Related Questions