This is something I made as an input display resembling NohBoard because I need to make a video for a video game, but it's on Linux which doesn't have any good input display programs. One thing I'm worried about is the way I'm making the scroll wheel flash last for exactly three frames in a 60 FPS recording. I checked how many frames it lasted by checking from a recording, and it seemed to be working fine, but I'm still not 100% sure if it's the best way to achieve making it last for exactly three frames in a 60 FPS recording.
import pygame
import pynput
import threading
def main():
lock_for_dict = threading.Lock()
lock_for_scroll = threading.Lock()
pygame.init()
win = pygame.display.set_mode((343, 229))
pygame.display.set_caption("test")
font = pygame.font.Font(pygame.font.get_default_font(), 15)
down_font = pygame.font.Font(pygame.font.get_default_font(), 14)
# {key_or_mouse_button: (rect_location_and_size, (black_key_text, white_key_text), key_text_location)}
# 0 and 1 are left mouse and right mouse
shown_keys = {
0: ((250,94,41,41), (font.render("LM", True, (0,0,0)), font.render("LM", True, (255,255,255))), (260,106)),
1: ((292,94,41,41), (font.render("RM", True, (0,0,0)), font.render("RM", True, (255,255,255))), (300,106)),
"a": ((72,94,41,41), (font.render("A", True, (0,0,0)), font.render("A", True, (255,255,255))), (87,106)),
"w": ((114,52,41,41), (font.render("W", True, (0,0,0)), font.render("W", True, (255,255,255))), (127,64)),
"s": ((114,94,41,41), (font.render("S", True, (0,0,0)), font.render("S", True, (255,255,255))), (129,106)),
"d": ((156,94,41,41), (font.render("D", True, (0,0,0)), font.render("D", True, (255,255,255))), (171,106)),
"q": ((72,52,41,41), (font.render("Q", True, (0,0,0)), font.render("Q", True, (255,255,255))), (87,64)),
"e": ((156,52,41,41), (font.render("E", True, (0,0,0)), font.render("E", True, (255,255,255))), (171,64)),
"r": ((198,52,41,41), (font.render("R", True, (0,0,0)), font.render("R", True, (255,255,255))), (213,64)),
"f4": ((198,10,41,41), (font.render("F4", True, (0,0,0)), font.render("F4", True, (255,255,255))), (210,22)),
"tab": ((10,52,61,41), (font.render("TAB", True, (0,0,0)), font.render("TAB", True, (255,255,255))), (25,64)),
"alt": ((72,178,61,41), (font.render("ALT", True, (0,0,0)), font.render("ALT", True, (255,255,255))), (87,190)),
"shift": ((10,136,81,41), (font.render("SHIFT", True, (0,0,0)), font.render("SHIFT", True, (255,255,255))), (28,148)),
"ctrl": ((10,178,61,41), (font.render("CTRL", True, (0,0,0)), font.render("CTRL", True, (255,255,255))), (20,190)),
"enter": ((134,178,81,41), (font.render("ENTER", True, (0,0,0)), font.render("ENTER", True, (255,255,255))), (148,190)),
"down": ((216,178,41,41), (down_font.render("down", True, (0,0,0)), down_font.render("down", True, (255,255,255))), (217,190))
}
held_or_released = dict()
for v in shown_keys.values():
pygame.draw.rect(win, (0,0,0), v[0])
win.blit(v[1][1], v[2])
scroll_up_text = (font.render("SU", True, (0,0,0)), font.render("SU", True, (255,255,255)))
scroll_down_text = (font.render("SD", True, (0,0,0)), font.render("SD", True, (255,255,255)))
scroll_time_remaining = [None, 0, 0] # [None, up, down]
def on_scroll(
x,
y,
dx,
dy,
lock_for_scroll_=lock_for_scroll,
scroll_time_remaining_=scroll_time_remaining
): # dy is 1 when scrolling up and -1 when scrolling down
with lock_for_scroll_:
scroll_time_remaining_[dy] = 42 # midpoint between 2 frames and 3 frames at 60 FPS
def on_click(
x,
y,
button,
pressed,
lock_for_dict_=lock_for_dict,
held_or_released_=held_or_released
):
with lock_for_dict_:
held_or_released_[button.name == "right"] = pressed
def on_press(
key,
lock_for_dict_=lock_for_dict,
held_or_released_=held_or_released,
shown_keys_=shown_keys,
hasattr_=hasattr,
str_lower=str.lower
): # this gets called repeatedly if the key is held down
if hasattr_(key, "char") and (k := key.char) is not None:
k = str_lower(k)
if k not in held_or_released_ and k in shown_keys_:
with lock_for_dict_:
held_or_released_[k] = True
elif hasattr_(key, "name") and (k := key.name) is not None:
if k not in held_or_released_ and k in shown_keys_:
with lock_for_dict_:
held_or_released_[k] = True
else:
with lock_for_dict_:
held_or_released_[("tab", "alt")[key.vk % 2]] = True
def on_release(
key,
lock_for_dict_=lock_for_dict,
held_or_released_=held_or_released,
shown_keys_=shown_keys,
hasattr_=hasattr,
str_lower=str.lower
):
if hasattr_(key, "char") and (k := key.char) is not None:
k = str_lower(k)
if k in shown_keys_:
with lock_for_dict_:
held_or_released_[k] = False
elif hasattr_(key, "name") and (k := key.name) is not None:
if k in shown_keys_:
with lock_for_dict_:
held_or_released_[k] = False
else:
with lock_for_dict_:
held_or_released_[("tab", "alt")[key.vk % 2]] = False
key_listener = pynput.keyboard.Listener(on_press=on_press, on_release=on_release)
mouse_listener = pynput.mouse.Listener(on_scroll=on_scroll, on_click=on_click)
key_listener.daemon = True
mouse_listener.daemon = True
key_listener.start()
mouse_listener.start()
pygame_time_wait = pygame.time.wait
pygame_display_update = pygame.display.update
pygame_draw_rect = pygame.draw.rect
win_blit = win.blit
held_or_released_items = held_or_released.items
held_or_released_clear = held_or_released.clear
pygame_QUIT = pygame.QUIT
pygame_event_get = pygame.event.get
any_ = any
while not any_(event.type == pygame_QUIT for event in pygame_event_get()):
waited_time = pygame_time_wait(1)
pygame_display_update()
with lock_for_scroll:
if scroll_time_remaining[1] >= 0:
scroll_time_remaining[1] -= waited_time
pygame_draw_rect(win, ((0,0,0), (255,255,255))[scroll_time_remaining[1] >= 0], (271,52,41,41))
win_blit(scroll_up_text[scroll_time_remaining[1] < 0], (281, 64))
if scroll_time_remaining[2] >= 0:
scroll_time_remaining[2] -= waited_time
pygame_draw_rect(win, ((0,0,0), (255,255,255))[scroll_time_remaining[2] >= 0], (271,136,41,41))
win_blit(scroll_down_text[scroll_time_remaining[2] < 0], (281, 148))
with lock_for_dict:
for k, v in held_or_released_items():
args = shown_keys[k]
pygame_draw_rect(win, ((0,0,0), (255,255,255))[v], args[0])
win_blit(args[1][not v], args[2])
held_or_released_clear()
pygame.quit()
main()