1

I'm currently coding a macro software where it records mouse movement, click and keyboard input. I'm using Tkinter for the GUI and pynput to detect input. So, in my gui I have two buttons, one for playing the record and one to record mouse and keyboard inputs. When i press record, i have to re-click on it to stop the record. But, pynput function loop make my Tkinter GUI freeze, so i set a thread to make my Tkinter window working while pynput function are running. The problem is when I stop the recording and then I play it, the record is 0.5x slower than the moment i was recording. I think it's because of time.time(), which is imprecise because of the thread. I'm actually blocked and i don't know how to fix this.

import threading
from tkinter import *
from tkinter.ttk import *

from macro import startRecord, stopRecord, playRec

# Window Setup
window = Tk()
window.title("MacroRecorder")
window.geometry("350x200")

def startRecordingAndChangeImg():
    global stopBtn
    recordBtn.pack_forget()
    stopBtn = Button(window, text="stop", command=stopRecordingAndChangeTxt)
    stopBtn.pack(side=RIGHT, padx=50)
    threading.Thread(target=startRecord).start()

def stopRecordingAndChangeImg():
    global recordBtn
    stopBtn.pack_forget()
    recordBtn = Button(window, text="record", command=startRecordingAndChangeTxt)
    recordBtn.pack(side=RIGHT, padx=50)
    stopRecord()

# Play Button
playBtn = Button(window, text="play", command=playRec)
playBtn.pack(side=LEFT, padx=50)

# Record Button
recordBtn = Button(window, text="record", command=startRecordingAndChangeTxt)
recordBtn.pack(side=RIGHT, padx=50)


window.mainloop()
from pynput import mouse, keyboard
from pynput.mouse import Button
from pynput.keyboard import Key
import time
import os
import json

mouseControl = mouse.Controller()
keyboardControl = keyboard.Controller()

def startRecord():
    global start_time
    global mouse_listener
    global keyboard_listener
    global macroEvents
    macroEvents = {'events': []}
    start_time = time.time()
    mouse_listener = mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll)
    keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release)
    print('record started')
    with mouse_listener, keyboard_listener:
        mouse_listener.join()
        keyboard_listener.join()

def stopRecord():
    mouse_listener.stop()
    keyboard_listener.stop()
    json_macroEvents = json.dumps(macroEvents, indent=4)
    with open(os.path.join("C:/Users/***/Desktop/autre/python project/macro-recorder/data/test.json"), "w") as macroRecord:
        macroRecord.write(json_macroEvents)
    print('record stopped')


def on_move(x, y):
    global start_time
    macroEvents["events"].append({'type': 'cursorMove', 'x': x, 'y': y, 'timestamp': time.time() - start_time})
    start_time = time.time()


# I use the same methods for each listener on_click, on_scroll, on_press and on_release, I reset the timer to 0

def playRec():
    print('record playing')
    for i in range(len(macroEvents["events"])):
        time.sleep(macroEvents["events"][i]["timestamp"])
        if macroEvents["events"][i]["type"] == "cursorMove":
            mouseControl.position = (macroEvents["events"][i]["x"], macroEvents["events"][i]["y"])
        elif macroEvents["events"][i]["type"] == "leftClickEvent":
            if macroEvents["events"][i]["pressed"] == True:
                mouseControl.press(Button.left)
            else:
                mouseControl.release(Button.left)
        elif macroEvents["events"][i]["type"] == "rightClickEvent":
            if macroEvents["events"][i]["pressed"] == True:
                mouseControl.press(Button.right)
            else:
                mouseControl.release(Button.right)
        elif macroEvents["events"][i]["type"] == "middleClickEvent":
            if macroEvents["events"][i]["pressed"] == True:
                mouseControl.press(Button.middle)
            else:
                mouseControl.release(Button.middle)
        elif macroEvents["events"][i]["type"] == "scrollEvent":
            mouseControl.scroll(macroEvents["events"][i]["dx"], macroEvents["events"][i]["dy"])
        elif macroEvents["events"][i]["type"] == "keyboardEvent":
            keyToPress = macroEvents["events"][i]["key"] if 'Key.' not in macroEvents["events"][i]["key"] else special_keys[macroEvents["events"][i]["key"]]
            if macroEvents["events"][i]["pressed"] == True:
                keyboardControl.press(keyToPress)
            else:
                keyboardControl.release(keyToPress)

I'm expecting to have mu GUI not freezing and pynput function working on his fullest.

Thanks in advance for you responses.

LOUDO
  • 19
  • 2
  • The big problem you face here is `time.sleep`. You didn't show us any actual data, but I'm guessing your events happen with millisecond spacing, and `time.sleep` doesn't work at a millisecond level. The MINIMUM sleep time is the scheduler interval, which on Windows is 16ms. If you sleep for 1ms, it will actually sleep for 16ms. You really need to accumulate events so you only play back every 30ms or so. After all, your screen doesn't refresh on a millisecond level, either. – Tim Roberts Aug 04 '23 at 18:03
  • https://stackoverflow.com/help/minimal-reproducible-example – Сергей Кох Aug 05 '23 at 16:11

0 Answers0