0

I can't figure out, how to make it work. I hope you will help me.

I edited the code, to make it more simple and short - that's why it's only printing something out, but it's enough to show the issue.

Everything is running fine, as long as I hit the enter button to stop functions and click the button (in Tkinter window) to start the main_function() again.

When I exit the program and run the function again - everything is fine. The problem only occur when I stop the function and run it the second time.

I hope I described the problem clearly and the comments in the code are helpful.

The error I receive:

Exception in thread Thread-5:
Traceback (most recent call last):
  File "path\threading.py", line 932, in _bootstrap_inner
   self.run()
  File "path\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "path/program.py", line 323, in fun_1
    temp_img = sct.grab(region)
  File "path\mss\windows.py", line 291, in grab
    raise ScreenShotError("gdi32.GetDIBits() failed.")

mss.exception.ScreenShotError: gdi32.GetDIBits() failed.

Code:

import threading
import keyboard
import time
import mss
import mss.tools

bot_stop = False


def main_function():   # I run this function by pressing button in tkinter window
    global bot_stop
    bot_stop = False

    # threading
    thread_1 = threading.Thread(target=fun_1)

    # start the thread if checkbox in tkinter window is ticked
    if checkbox_1.get():
        thread_1.start()

    # press enter to stop the main_function and fun_1, 'bot_stop_switch' is a callback
    keyboard.on_press_key('enter', bot_stop_switch)

    # run this loop as long as user will press "enter"
    while not bot_stop:
        print("main_function_is_running")
        time.sleep(1)


def fun_1():

    # make a screenshoot of selected region (this case: upper left main monitor corner)
    with mss.mss() as sct:
        region = {'top': 0, 'left': 0, 'width': 100, 'height': 100}
        temp_img = sct.grab(region)
        raw_bytes_1 = mss.tools.to_png(temp_img.rgb, temp_img.size)

    # check if something changed on the screen in the selected region as long as user will press "enter"
    while not bot_stop:
        temp_img = sct.grab(region)
        raw_bytes_2 = mss.tools.to_png(temp_img.rgb, temp_img.size)
        if raw_bytes_1 != raw_bytes_2:
            print("something changed in the screen corner!")
        time.sleep(0.5)


def bot_stop_switch(keyboard_event_info):
    global bot_stop

    bot_stop = True

    keyboard.unhook_all()
    print(keyboard_event_info)

martineau
  • 119,623
  • 25
  • 170
  • 301
Dawid Rutkowski
  • 410
  • 3
  • 8
  • Tkinter doesn't support multithreading in the sense that only one thread — often the main one — can utilize it. If other threads attempt to do _anything_ directly related to it, you'll likely encounter problems… – martineau Apr 07 '20 at 18:43
  • I had the same issue, when I did it "outside" Tkinter by writing similar, testing code. – Dawid Rutkowski Apr 07 '20 at 18:55
  • 1
    It's quite possible the one or more of the other modules you're using are also not thread-safe (like `keyboard` or `mss`). – martineau Apr 07 '20 at 19:26
  • What would be the best way to resolve it? Anyway I can't understand why it works the first time after I run the program and is not working when I stop and run the function again... – Dawid Rutkowski Apr 07 '20 at 21:15
  • 1
    Glad you found a workaround. After thinking about the situation some more, I now believe the issue is `tkinter` and the `keyboard` module are generally incompatible because both do fairly low-level monitoring of the mouse and keyboard input devices. I believe I've seen other questions here about that subject if you search for them. Would have realized it sooner, but it's easy to forget your question is tkinter-related at all because it's not tagged "tkinter" and that fact is otherwise hardly mentioned. I'm going to add the tag for future readers… – martineau Apr 08 '20 at 00:00

1 Answers1

0

After a few hours of testing I found a solution, but I have no idea, why it makes any difference. The point is to make the first screenshoot temp_img = sct.grab(region) inside the main function (from where we are starting a thread for the second function).

import threading
import keyboard
import time
import mss
import mss.tools

bot_stop = False
raw_bytes_1 = None
region = {}


def main_function():   # I run this function by pressing button in tkinter window
    global bot_stop
    global raw_bytes_1
    global region

    bot_stop = False

    # make a screenshoot of selected region (this case: upper left main monitor corner)
    with mss.mss() as sct:
        region = {'top': 0, 'left': 0, 'width': 100, 'height': 100}
        temp_img = sct.grab(region)
        raw_bytes_1 = mss.tools.to_png(temp_img.rgb, temp_img.size)

    # threading
    thread_1 = threading.Thread(target=fun_1)

    # start the thread is checkbox in tkinter window is ticked
    if checkbox_1.get():
        thread_1.start()

    # press enter to stop the main_function and fun_1
    keyboard.on_press_key('enter', bot_stop_switch)

    # run this loop as long as user will press "enter"
    while not bot_stop:
        print("main_function_is_running")
        time.sleep(1)


def fun_1():
    # check if something changed on the screen in the selected region as long as user will press "enter"
    while not bot_stop:
        temp_img = sct.grab(region)
        raw_bytes_2 = mss.tools.to_png(temp_img.rgb, temp_img.size)
        if raw_bytes_1 != raw_bytes_2:
            print("something changed in the screen corner!")
        time.sleep(0.5)


main_function()
Dawid Rutkowski
  • 410
  • 3
  • 8