I would like to simulate typing of huge text. I’ve tried to use pyautogui to do that, but I’t didn’t worked properly since the computer was not detecting the keys being pressed:
pyautogui.typewrite('I love python! It is such an amazing language.')
I couldn’t think of another way to do that than using pynput.keyboard library, but as I have a huge text, that would not be a viable option. I don’t know if there is a way to simulate the keys other than create a line for each letter.
keyboard = Controller() keyboard.press('I') keyboard.release('I') keyboard.press('L') keyboard.release('L') keyboard.press('O') keyboard.release('O') keyboard.press('V') keyboard.release('V')
Is there a way to simulate keys being pressed to write a text without having to create a single line for each letter that the script would have to type?
Advertisement
Answer
The code below demonstrates that there is a way to simulate keys being pressed to write a text without having to create a single line for each letter that the script would have to type.
In addition to that the provided solution allows to execute code placed in the same script after the code for automated typing which feature is used to show the automated keyboard input in a typing speed test GUI:
import pynput import random import threading import time txt = "pynput is typing like a human" #txt = "abc" class Sim_keyb_typing(threading.Thread): def __init__(self, text, strt_delay=1.5, dct_delays={('a','i'):0.15}, delayrange=(0.1, 0.3) ): threading.Thread.__init__(self) self.text = text self.strt_delay = strt_delay self.last_char = " " self.dct_delays = dct_delays self.delayrange = delayrange self.ppkbC = pynput.keyboard.Controller() def run(self): time.sleep(self.strt_delay) for char in self.text: delay = self.dct_delays.get( (self.last_char, char), random.uniform(*self.delayrange) ) time.sleep(delay) self.ppkbC.type(char) self.last_char = char # Let's wait 1.5 seconds and then start typing: skt = Sim_keyb_typing(txt) skt.start() # Wait for Sim_keyb_typing(txt) to finish #skt.join() # ====================================================================== import tkinter import time import threading import random import string class simpleTypeSpeedGUI: # creates tkinter GUI mainloop def __init__(self, txt): self.root = tkinter.Tk() self.root.title("Type Speed Test (starts on first keypress)") self.root.geometry("1600x400") self.text_lines = txt.split("n") self.text_line = random.choice(self.text_lines) self.len_text_line = len(self.text_line) # Label showing text to type: self.sample_label = tkinter.Label(self.root, text=self.text_line, font=("Helvetica", 14)) self.sample_label.grid( row=0, column=1, columnspan=2, padx=15, pady=15, sticky='w') # Text box receiving typing input: self.input_textbox = tkinter.Entry(self.root, width=120, font=("Helvetica", 14)) self.input_textbox.grid(row=1, column=1, columnspan=2, padx=15, pady=15) # Registering callback function for text box keyboard input keypress self.input_textbox.bind("<KeyRelease>", self.on_key_up) # The reason for KeyRelease is to have updated widget content in the callback function self.input_textbox.focus() # Label showing typing speed: self.speed_label = tkinter.Label(self.root, text="Typing speed: n---- n---- n---- n---- ", font=("Helvetica", 12)) self.speed_label.grid(row=2, column=1, columnspan=2, padx=5, pady=10) # Button for restart/reset of typing: self.reset_button = tkinter.Button(self.root, text="Reset", command=self.reset, font=("Helvetica", 18)) self.reset_button.grid(row=3, column=1, columnspan=2, padx=15, pady=15) # Standard value for average number of characters in a word self.std_char_per_word = 5 # used to calculate words per minute from typing speed in chars/min # initialisation of code flow control values: self.start_time = None self.end_time = None self.printable = string.printable[:-5] self.root.mainloop() def on_key_up(self, event): # processing on key release because on key press results # in the widget content are not yet updated with the key char. if event.keycode == 9: # pressed Escape => EXIT print("nEscape-EXIT") self.root.destroy() return # <- required because root.destroy() doesn't block # execution of further code which then runs into # trouble as .destroy() makes widgets not available if self.start_time is None: self.start_time = time.perf_counter() input_textbox = self.input_textbox.get() # print(f'{event.keycode=} => "{event.char=}" ') if input_textbox == self.text_line: self.running = False self.input_textbox.config(fg="green") self.end_time = time.perf_counter() else: if self.text_line.startswith(input_textbox): self.input_textbox.config(fg="black") else: self.input_textbox.config(fg="red") self.show_typespeed() def show_typespeed(self): curr_time = time.perf_counter() if self.start_time is not None and self.end_time is not None: time_diff = self.end_time - self.start_time elif self.start_time is not None and self.end_time is None: # and self.end_time is None time_diff = curr_time - self.start_time elif self.start_time is None and self.end_time is None: self.speed_label.config(text="Speed: n---- CPSn---- CPMn---- WPSn---- WPS") return cps = self.len_text_line / time_diff cpm = cps * 60.0 wps = cps / self.std_char_per_word wpm = wps * 60.0 self.speed_label.config(text=f"Speed: n{cps:5.2f} CPSn{cpm:5.2f} CPMn{wps:5.2f} WPSn{wpm:5.2f} WPM") def reset(self): self.start_time = None self.end_time = None self.text_line = random.choice(self.text_lines) self.len_text_line = len(self.text_line) self.sample_label.config(text=self.text_line) self.input_textbox.delete(0, tkinter.END) self.show_typespeed() sTSG = simpleTypeSpeedGUI(txt)