python xlib xgrabkey keyrelease events not firing - python

i want to catch keydown and keyup events with python xlib, but keyup events disappear when some keys are pressed simultaneously.
if 2 or more keyes are released simultaneously then there will be 2 or more keypress events, but only 1 keyrelease event.
for this to happen the keys don't even have to be released simultaneously, for example if you enter this sequence fast:
press A
press B
release A
release B
will yield only 1 keyrelease for A
press A
press B
release B
release A
will yield 2 keyreleases
from Xlib import X,XK
from Xlib.display import Display
import signal,sys
root = None
display = None
def grab_keyname(n):
global root
keysym = XK.string_to_keysym(n)
keycode = display.keysym_to_keycode(keysym)
root.grab_key(keycode, X.AnyModifier, False,X.GrabModeSync, X.GrabModeAsync)
def main():
# current display
global display,root
display = Display()
root = display.screen().root
root.change_attributes(event_mask = X.KeyPressMask|X.KeyReleaseMask)
grab_keyname("j")
grab_keyname("k")
grab_keyname("l")
signal.signal(signal.SIGALRM, lambda a,b:sys.exit(1))
signal.alarm(4)
while True:
event = display.next_event()
print event.type
main()

Even though this question is 7 years old, a solution for anyone stumbling upon this:
This is a 'bug' in Xorg (which is apparently intentional) which causes keyboard grab to stop upon a key release, and only to start again on another button press. Therefore, any events in between (-> the second key release event) are lost. See https://bugs.freedesktop.org/show_bug.cgi?id=99280
The solution proposed there is to have a counter which indicates how many of the keys are still pressed, and manually grab the keyboard if there are still pending presses. In addition, the keyboard needs to be ungrabbed after the last release event.
The implementation might look like the following:
keys_pressed = 0
# ...
event = display.next_event()
if event.type == X.KeyPress:
keys_pressed += 1
elif event.type == X.KeyRelease:
if keys_pressed != 0:
keys_pressed -= 1
if keys_pressed == 0:
display.flush()
display.ungrab_keyboard(X.CurrentTime)
else:
root.grab_keyboard(False, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
Note the check for keys_pressed != 0 in the release branch: this is because after ungrabbing the keyboard, one additional release event is caught (there is probably a way to prevent this, but it does not matter for my use case...)

Related

How to record mouse and keyboard movement simultaneously, and store it as a variable that could be placed in a function or loop with Python?

So I want to start a project that records my keyboard input and mouse movement for a sandbox PC games. I love farming, however whether I'm tired, and taking a short break to make a coffee or have a phone call. When I come back my farm gets annihilated.
I tried and made this one, but it seems not to work. Any way to make it better with the possibility to implement the recorded inputs in a loop function?
import pyautogui
def record_movement():
# Initialize variables to store mouse and keyboard events
mouse_events = []
keyboard_events = []
# Start recording mouse and keyboard events
pyautogui.PAUSE = 0.001 # Set a small pause to record events more accurately
pyautogui.FAILSAFE = False # Disable failsafe feature to allow for recording
recording = True
while recording:
event = pyautogui.getEvent()
if event.event_type == 'MouseMove':
mouse_events.append(event)
elif event.event_type == 'KeyDown':
keyboard_events.append(event)
elif event.event_type == 'KeyUp':
keyboard_events.append(event)
elif event.event_type == 'Quit':
recording = False
return mouse_events, keyboard_events
def replay_movement(mouse_events, keyboard_events):
# Replay recorded mouse and keyboard events
for event in mouse_events:
pyautogui.moveTo(event.x, event.y)
for event in keyboard_events:
if event.event_type == 'KeyDown':
pyautogui.press(event.name)
elif event.event_type == 'KeyUp':
pyautogui.keyUp(event.name)
# Test:
mouse_events, keyboard_events = record_movement()
replay_movement(mouse_events, keyboard_events)

How can I make it so that something happens in pygame when I type a number [duplicate]

This question already has answers here:
How to get keyboard input in pygame?
(11 answers)
Closed 1 year ago.
My code is:
keys = pygame.key.get_pressed()
if keys[pygame.K_1]:
if money >= worker_cost:
money -= worker_cost
workers += 1
worker_cost = round(worker_cost*1.1, 2)
So I am trying to make a game in pygame, but when I put this in to detect when the user wants to buy something, it won't detect that I pressed anything. I have tried putting print commands inside the if loop to see if the loop starts, but it doesn't. Any tips would be appreciated
Use the keyboard events instead of pygame.key.get_pressed().
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a key and get continuous movement.
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_1:
# do action
if money >= worker_cost:
money -= worker_cost
workers += 1
worker_cost = round(worker_cost*1.1, 2)
# [...]

How to make text move continuously by pressing a button on CodeSkulptor (Python)?

I'm trying to make the text move continuously by pressing a button but right now it only moves an interval each time it is pressed.
def move():
global y
global checkmove
checkmove = True
if y > 280:
y = 0
else:
y += 2
There is no built-in way to tell if a button is being held down. However, there is a button_up handler as well as a button_down handler. If you use timers, you can tell how long a key has been held down. Here is a sample of code that can tell which keys are held down. Multiple keys can be held down at once. If you want to change the timing, you can change the length of the timers at the bottom of the code. Changing the value of timer 1 will change how long the key has to be held before simulating keypresses, changing the length of timer 2 changes how quickly the keys are pressed once held down. You can also see the code here: https://py3.codeskulptor.org/#user303_gtJ15kIGNV_0.py
def timer_handler():
#start timer 2 if it is off
if not timer_on:
timer2.start()
#stop timer 1
timer.stop()
def timer2_handler():
#simulates repeated keypresses if the key is held down
global timer_on
timer_on=True
if len(held_keys)==0:
timer2.stop()
timer_on=False
else:
for item in held_keys:
action(item)
def action(key):
#this is where all the code to make something happen goes
if key==upkey:
#do something
print('up key pressed')
elif key==downkey:
#do something else
print('down key pressed')
def key_handler(key):
#handles user keypresses
#add key to held keys
global held_keys
held_keys.append(key)
#do something with key
action(key)
#start timer 1 if timer 2 is off
if not timer_on:
timer.start()
def release_handler(key):
#handles key releases
#remove the key from the list
global held_keys
if key in held_keys:
held_keys.remove(key)
#if no keys are held, stop both timers
global timer_on
if len(held_keys)==0:
timer.stop()
timer2.stop()
timer_on=False
import simplegui
timer_on=False
held_keys=[]
#starts timer 2
timer = simplegui.create_timer(300, timer_handler)
#automatic keypress
timer2 = simplegui.create_timer(100, timer2_handler)
#map whatever keys you need
spacekey=simplegui.KEY_MAP['space']
leftkey=simplegui.KEY_MAP['left']
rightkey=simplegui.KEY_MAP['right']
upkey=simplegui.KEY_MAP['up']
downkey=simplegui.KEY_MAP['down']
#create a frame and set key down/up handlers
frame=simplegui.create_frame('Click the box, then use arrow keys to move', 100, 100)
frame.set_keydown_handler(key_handler)
frame.set_keyup_handler(release_handler)
frame.start()

How can I detect keyboard input to play a sound with python?

I am making a program that will play a sound when you press a key (or just type) on the keyboard.
I just started to work on it, and I am trying to use pygame.key.get_pressed() to check for keyboard input. I got to here with the code:
from pygame import init, key
init()
spam = []
while True:
spam = key.get_pressed()
if True in spam:
print('KEY PRESSED')
elif False in spam:
print('NONE')
It works by checking if a True value is in spam (spam is the name of the list pygame returns). key.get_pressed returns a list of True/False values for every key, True if pressed, False if not.
The problem with this code is that when I run it, it only outputs None. This means that I am not detecting the keyboard input.
If anyone knows how to fix this, or a better way to do it, I would greatly appreciate it!
Thanks!
pygame.key.get_pressed() gets the states of all keybord buttons. It returns a list.
But, note the values which are returned by pygame.key.get_pressed() are only updated when the key event is get events from the queue by pygame.event.get(). In simple words, pygame.key.get_pressed() only works, if there is an event loop, too.
You can evaluate a specific key (e.g. space key):
allKeys = pygame.key.get_pressed()
spam = allKeys[pygame.K_SPACE]
If you want to evaluate if any key is pressed, then you've to evaluate if any() value in allKeys is True:
allKeys = pygame.key.get_pressed()
anyKey = any([k for k in allKeys if k == True])
Another solution would be to check if the KEYDOWN event occurred. The next pending event can be fetched by pygame.event.get():
keyPressed = False
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
keyPressed = True

How to efficiently hold a key in Pygame?

I've found two related questions:
Pygame hold key down causes an infinite loop
pygame - on hold button down
But I want to be specific. How to?
while not done:
for e in event.get():
if e.type == KEYDOWN:
keys = key.get_pressed()
if e.type == QUIT or keys[K_ESCAPE]:
done = True
if keys[K_DOWN]:
print "DOWN"
When I press the down arrow, it prints, but it prints just once. If I want to print it another time, I need to press it again.
If I use the while keyword instead,
while keys[K_DOWN]:
print "DOWN"
I get an infinite loop for some obscure reason.
This logical alternative is also useless:
if ((e.type == KEYDOWN) and keys[K_DOWN]):
print "DOWN"
And there is this other one that somehow cleans the events and you can use while:
while not done:
for e in event.get():
if e.type == KEYDOWN:
keys = key.get_pressed()
if e.type == QUIT or keys[K_ESCAPE]:
done = True
while keys[K_DOWN]:
print "DOWN"
event.get()
keys = key.get_pressed()
But you press the down key for less than one second and it prints thousands of times. (Moving a player would be impossible, and adjusting clock for this does not seem to be the right way to deal with it (And I've tried and I've failed miserably.)).
To press and execute the block thousands of times is useless. What I want, is to press the key and keep going with the action while I don't release it, within the defined game clock speed.
Don't mix up event.get() and key.get_pressed().
If you press or release a key, and event is put into the event queue, which you query with event.get(). Do this if you're actually interested if a key was pressed down physically or released (these are the actual keyboard events. Note that KEYDOWN get's added multiple time to the queue depending on the key-repeat settings).
Also, there's no need to query the state of all keys while handling a KEYDOWN event, since you already know which key is pressed down by checking event.key
If you're interested in if a key is hold down (and ignoring the key-repeat, which you probably want), then you should simply use key.get_pressed(). Using a bunch of flags is just unnecessary and will clutter up your code.
So your code could simplified to:
while not done:
keys = key.get_pressed()
if keys[K_DOWN]:
print "DOWN"
for e in event.get():
pass # proceed other events.
# always call event.get() or event.poll() in the main loop
I am not familiar with Pygame, but, as I see, the program in it should have an event-based architecture. Unless you get the incoming events and process them, nothing happens. That's why your simple loop becomes infinite: it just does not process events.
while keys[K_DOWN]: # Nobody updates the keys, no events are processed
print "DOWN"
Then concerning the get_pressed() call. What it returns is a list of keys. So, you are trying to just loop until the key is released. That's a problem. According to this, pygame.event.get() returns immediately even if there are no events in the queue. The call to get() means: my code still has what to do, but I don't want to block the events, so please process the pending events before I continue. If your code is just waiting for an event, that means it has nothing to do.
The function to WAIT (without blocking the inner loop of Pygame) for an event is pygame.event.wait() (the logic is: I have nothing to do in my code until something happens). However, if you use it, you will have to get information about keys pressed or released from the event itself, not from get_pressed().
Here is an example from the comments to the doc page:
for event in pygame.event.get() :
if event.type == pygame.KEYDOWN :
if event.key == pygame.K_SPACE :
print "Space bar pressed down." #Here you should put you program in the mode associated with the pressed SPACE key
elif event.key == pygame.K_ESCAPE :
print "Escape key pressed down."
elif event.type == pygame.KEYUP :
if event.key == pygame.K_SPACE :
print "Space bar released."
elif event.key == pygame.K_ESCAPE :
print "Escape key released." #Time to change the mode again
I like to take a slightly different approach to this problem. Instead of checking if the key is pressed and taking some action when it is, set a flag on key down and unset it on key up. Then in the function to update the player's position, check the flag and update accordingly. The following pseudo-Python explains what I'm getting at:
if down_key_pressed:
down_flag = True
elif down_key_released:
down_flag = False
elif right_key_pressed:
etc...
This should be done in a separate loop that takes the player's input. Then in update_player_position() you can do:
if down_flag:
move_player_down()
elif right_flag:
move_player_right()
This example assumes four-directional movement, but you could extend it to eight-directional fairly easily by just using if down_flag and right_flag instead.
You can get keydown event repeatedly if you use pygame.key.set_repeat(# millisecond) to set the time limitation for each key event. Quote: when the keyboard repeat is enabled, keys that are held down will generate multiple pygame.KEYDOWN events. The delay is the number of milliseconds before the first repeated pygame.KEYDOWN will be sent. After that another pygame.KEYDOWN will be sent every interval milliseconds. If no arguments are passed the key repeat is disabled. When pygame is initialized the key repeat is disabled. please see following link for detail http://www.pygame.org/docs/ref/key.html#pygame.key.set_repeat
I am using a different approach on holding down a key that I am using in the specific task of moving an object left or right.
I do not care about the computer knowing that a key is actually held down.
When I press a key, a variable that I define for that key (EG: left arrow) is set to True.
Until that key is unpressed (with the event pygame.KEYUP) the "movement to left" is performed.
Dominik's solution is the perfect one (separating event.get() from the keyboard ones). It works perfectly! Finally, no more problems with pygame's input.
Flags:
flag = False # The flag is essential.
while not done:
for e in event.get(): # At each frame it loops through all possible events.
keys = key.get_pressed() # It gets the states of all keyboard keys.
if e.type == QUIT:
done = True
if e.type == KEYDOWN: # If the type is KEYDOWN (DIFFERENT FROM "HELD").
if keys[K_DOWN]: # And if the key is K_DOWN:
flag = True # The flag is set to true.
elif e.type == KEYUP: # The very opposite.
if keys[K_DOWN]:
flag = False
if flag == True: # DON'T USE "while flag == true", it'll crash everything. At every frame, it'll check if the flag is true up there. It's important to remember that the KEYDOWN worked ONLY ONCE, and it was enough to set that flag. So, at every frame, THE FLAG is checked, NOT THE KEYDOWN STATE. Every "player movement" while a key is being held MUST be done through flags.
print "DOWN"
This is basically How I did it, well Go to www.cswonders.com, and it might help you . Go to level 3, then 3.6, and go through the tutorial. I learned it from there. Here is my code.
def update(self):
self.speedx = 0
self.speedy = 0
# If left or right key is pressed, move left or right
pressed_key = pygame.key.get_pressed()
if pressed_key[pygame.K_LEFT]:
self.speedx = -10
if pressed_key[pygame.K_RIGHT]:
self.speedx = 10
if pressed_key[pygame.K_UP]:
self.speedy = -10
if pressed_key[pygame.K_DOWN]:
self.speedy = 10
self.rect.x += self.speedx
self.rect.y += self.speedy
# No further move if off screen
if self.rect.right > SCREEN_WIDTH:
self.rect.right = SCREEN_WIDTH
if self.rect.left < 0:
self.rect.left = 0

Categories

Resources