0

I'm currently trying to create a program where it detects the mouse movement on my computer and pops up a window if any movement is detected. However, I've run into some issues with Tkinter threading. I have read multiple other posts on the same issue and understand that Tkinter isn't thread-safe and that it can only be run in the main thread. However, I'm still unsure about the fix for my program. I'd appreciate it if someone could help me take a look.

Essentially, my (simplified) program has two scripts, access_detection.py and password_prompt.py. The first file is responsible for using pynput.mouse to track mouse movement, and the second file is responsible for popping up a Tkinter window and prompting the user for a password whenever mouse movement from the first script is detected.

> password_prompt.py (pops up a window for the user to enter their passcode)

def confirm_passcode(self):
    self.access_granted = True

def prompt_password(self) -> bool:
        # initialize window
        entry = Entry(self.win, width=25, textvariable=self.password, show="*")
        entry.pack(pady=((int(self.win.winfo_screenheight()/2) - 100), 0))
        button = ttk.Button(self.win, text="Confirm Password", command=self.confirm_passcode)
        button.pack(pady=10)

        while True:
            self.win.attributes('-fullscreen', True)
            self.win.attributes('-topmost', True)

            if self.access_granted == True:
                return True
            elif self.access_granted == False and self.access_attempts >= 3:
                return False

            self.win.update()

> access_detection.py (tracks mouse movement)

def on_move_mouse(self, x, y):
    self.security_passed = popup_obj.prompt_password()

    if self.security_passed == False:
        # do something here
    else:
        # stops this program

def detection_start(self):
    with mouse_listener(on_move=self.on_move_mouse) as mouse_monitor:
        mouse_monitor.join()

# driver
if __name__ == "__main__":
    monitor = # a class with detection_start()
    monitor.detection_start()

The issue raised: RuntimeError: main thread is not in main loop

Currently, the main method is in access_detection.py and I believe the pynput.mouse threading is using the main thread, causing the error since Tkinter isn't thread-safe. Is there a way that I could have these two threads running simultaneously and still accomplish the goal of tracking mouse movement and popping up a window when movement is detected? (with Tkinter in the main thread if it has to be necessary).

The original code is a bit lengthy. It could be found here if you need more info. (ignore main.py)

Haru Frost
  • 237
  • 1
  • 12
  • Is this part of a larget tkinter app or are you only using tk in this one case for the popup? From the code shown, you haven't called the applicatoin's `.mainloop()` so the tk event loop isn't running. You also are not running multiple threads. If you already have a mainloop running, the mouse detector should run in a different thread and should use the app's `event_generate`. See for example https://stackoverflow.com/questions/270648/tkinter-invoke-event-in-main-loop – tdelaney Nov 20 '21 at 06:34
  • I'm calling `win.update()` in a while loop instead of calling `.mainloop()`. I did this cause I wanted prompt_password() to return a boolean and I can't seem to return anything while calling `.mainloop()`. I have tested `.update()` and it seems to be working properly (with the while loop). Anyhow, what do you suggest is the issue if the program isn't running multithreading? (I'm very inexperienced with this) – Haru Frost Nov 20 '21 at 06:40
  • `pynput.listener` uses `Thread` to run it - you would have to use `Queue` to send message from `on_move_mouse` to main thread which should run `prompt_password` and use `queue` to send back answer - and then `on_move_mouse` should continue. And `tkinter` in main thread could use `root.after` to run periodicaly function which check `queue` and if it get message then it run `prompt_password` – furas Nov 20 '21 at 12:25
  • or you should create `Tk()` in `prompt_password` and then all GUI will run in the same thread. – furas Nov 20 '21 at 12:47

0 Answers0