moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.81k stars 251 forks source link

value for key.space is ' ' #609

Open hoefkensj opened 2 months ago

hoefkensj commented 2 months ago

Description when using pynput listener , the key returned by the callback for the key , has its value set to ' ' whereas other keys use the PS/2 keycode instead (for the keys that have the value property set) think it would make more sense to either include space key with the keys that only have char defined , or leave it as it is but replace the value of the key to be 32

Platform and pynput version Distro : Gentoo Linux 2.14 , kernel : 6.6.8 Python : Python 3.11.7 , Pynput 1.7.7

To Reproduce

from pynput import keyboard
c=keyboard.Controller()
l = keyboard.Listener(on_press=lambda p:print(repr(p.value)))
l.start()
c.tap(keyboard.Key.space)  
c.tap(keyboard.KeyCode(32))

outputs:

' '
' '

expected:

32
32
oOosys commented 1 month ago

What makes you expect it to be 32?

hoefkensj commented 1 month ago

maybe i wasnt really to clear but in the case (as it is now) i would expect it to be 32 as that is the PS/2 keycode for space (the value of the key the keyboard sends ) all other keys that have the key.value properties use the ps/2 code in their value field.

however not all keys have the value property , specifically the ones with the key.char property usually dont , they instead send the character they represent, wich is what space now does but in the value property

so key.space now sends key.value=' '

wich is not consistent with all the rest of the keys two solutions that would make it valid would be: changing the property from value to char: key.char=' ' changing the char to a value: key.value=32

hoefkensj commented 1 month ago

tired to make it clearer what i meant hope this works as my explaining it is not 100% :) image

code used for creating that image:

from pynput import keyboard
from signal import pause

def kd(k):
    print(str(k).split('.')[-1], end='')
    if 'char' in dir(k):
        print(f'\x1b[15G\x1b[32m{k.char}\x1b[m', end='')
    else:
        print(f'\x1b[15G\x1b[31mX\x1b[m', end='')
    if 'value' in dir(k):
        print(f'\x1b[30G\x1b[32m{k.value}\x1b[m', end='')
    else:
        print(f'\x1b[30G\x1b[31mX\x1b[m', end='')
    print()

print('KEY\x1b[15GCHAR\x1b[30GVALUE')
with keyboard.Listener(on_press=kd) as s:
    while True:
        pause()

as you can see the space is the only one displaying a char in the value row... so i think it should be either showing the value there, or it should not have a value but a char(' ') instead ?

oOosys commented 1 month ago

My test run of the provided code gives:

ctrl          X              <65507>
<269025067>   None           X
cmd           X              <65515>
space         X              ' '
alt_gr        X              <65406>
<65027>       None           X

If I see it right there are more inconsistencies in the logic of the keycode, the character and the value than in the case of the space character. So what? I suggest to take it as it is as granted as it is probably not worth the effort to redefine it and brake this way some code written in the past based on what is.

hoefkensj commented 1 month ago

no it actually is pretty consistent, control keys send a value , printable characters send a char ,.. only space sends a char as a value ... and yes it matters also the reason for the keycode that is send as value is not just a random number but spot the connection

dunno how else to show but hopefully this will make it much more clear:

from pynput import keyboard
from signal import pause
import time,os
from contextlib import suppress

def pval(y,k):
    print(f'\x1b[{y};1H{str(k).split(".")[-1]}', end='')
    print(f'\x1b[30G\x1b[32m{k.value}\x1b[m', end='')
    # note:
    # the reason that suppress here is required is exactly the reason of my issue report
    with suppress(Exception):
        print(f'\x1b[50G\x1b[34m{hex(int(str(k.value)[1:-1]))}\x1b[m', end='',flush=True)
    print()

def send(key):
    with suppress(Exception):
        keyboard.Controller().tap(key)

def kd(k,pressed=[None,]):
    if k not in pressed and 'value' in dir(k):
        pressed+=[k]
        pval(len(pressed),k)

print('\x1b[2J\x1b[1;1HKEY\x1b[15GVALUE\x1b[50GHEX',flush=True)
with keyboard.Listener(on_press=kd) as s:
    for key in [*keyboard.Key]:
        send(key)
        time.sleep(0.1)
    pause()

result: image also take a look at the orange frames i put on it , if you go and look up an old ASCII table you might see some similarities, aswell as if you know ANSI escape codes like \x1b[ you see that 1b is also the code send by the actual escape key in hex ,so is the ansi tab code identical in hex value to the actual code send by the keyboard for tab... hence no the numbers are not random and therefor i was suggesting using decimal 32 or \x20 for space if it had to be a value and not a char since:

backspace = 0xFF08 -> chr(ord('\x08')) tab = 0xFF09 -> chr(ord('\x09')) ... space = 0xFF20 -> chr(ord('\x20'))

would also make sense inmy opinion

oOosys commented 1 month ago

It's all pure Python code only ... but ... somehow I failed to find the lines in code which decide about the assignment of values to the event properties and failed also to develop an idea how could it come that space is treated differently. Maybe you can try?

hoefkensj commented 1 month ago

found it but havent found a solution that works for all cases yet, since the cause was actually a hack put in especially for space : simi-fix now looks like image removed char=' ' from space = KeyCode._from_symbol('space') in _xorg.py

however this breaks every code out there that uses : key == ' ' to match for space a different solution could be to make space both have a char and a value set , with char=' ' and value = 0x20 have to take a deeper look into it and see if its only the case on linux as a platform or and if how its done on different platforms

oOosys commented 1 month ago

Yes ... breaking past code is what I have already mentioned as reason for not touching it, in spite of the fact it shoudn't be this way. Once it has happened the bug becomes a feature and with plenty of code relying on such behavior there is no way to get it right. It's like Guido van Rossum who was not able to take away already released features in Python he thought in the long run they shouldn't be there ... if they are there, they will be used and then there is no chance to get rid of them.

hoefkensj commented 4 weeks ago

yeah i have been looking into , adding both .char and .value to the space key , then overload eq to check for both, also tought other keys might benefit from that behavior, or something similar (eg enter matching both the ascii keycode , aswell as '\n' and '\cr\lf') but im running lost in the code where half he time wer passing around classes instead of objects so it seems wich kind of makes it hard for me to wrap my head around the whole

ps every key could just have a .value since it would be just the keyboardcode send by that key. just not every key has a corresponding char...