I have the following code in which I have an infinite loop that updates the state of a variable running in a thread, and in another thread, I have an infinite loop that prints the state of the said variable each second, the problem is that although the state updates correctly in the first thread, the state remains unaffected in the second one.
What could I do to solve this? I thought that using threads would solve the issue, also I'm not that well versed in asynchronous programing
inside main.py
import cv2 as cv
from utils import WindowCapture
import numpy as np
import os
import time
from threading import Thread
from threading import Event
import pyautogui
from queue import Queue
os.chdir(os.path.dirname(os.path.abspath(__file__)))
wincap = WindowCapture("DragonBallOnline")
global toggle
global enemyState
# >>> pyautogui.mouseDown(); pyautogui.mouseUp() # does the same thing as a left-button mouse click
# >>> pyautogui.mouseDown(button='right') # press the right button down
# >>> pyautogui.mouseUp(button='right', x=100, y=200) # move the mouse to 100, 200, then release the right button up.
def targetIsFullHealth(enemy):
return (enemy == np.array([65, 27, 234])).all()
def targetIsDead(enemy):
return (enemy == np.array([26, 10, 95])).all()
def targetIsUndefined(enemy):
return (enemy != np.array([65, 27, 234])).all() and (enemy != np.array([97, 155, 146])).all()
def combatLoop(): # unused
## target enemy
## if target is full health attack
## if target is dead wait and loot
## if target is undefined search new target
pass
def bot(): # unused
# Debug function not used currently
while True:
##do stuff
EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
Items = wincap.get_screenshot(621, 163, 662, 749)
Radar = wincap.get_screenshot(1750, 53, 157, 157)
cv.imshow("EnemyLife", EnemyLife)
cv.imshow("Items", Items)
cv.imshow("Radar", Radar)
# print(EnemyLife[10][5])
if (EnemyLife[10][5] == np.array([65, 27, 234])).all():
print("target is full health")
rotateCursor()
elif (EnemyLife[10][5] == np.array([97, 155, 146])).all():
print("Target is dead")
else:
print("No target")
if cv.waitKey(1) == ord("-"):
cv.destroyAllWindows()
break
print("Done!")
def rotateCursor(): # unused
# pyautogui.mouseDown(button="right")
# pyautogui.mouseUp(button="right", x=10, y=1)
pass
def updateEnemyState(threadname):
global toggle
global enemyState
toggle = True
while True:
EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
cv.imshow("EnemyLife", EnemyLife)
enemy = EnemyLife[10][5]
if targetIsDead(enemy):
enemyState = "dead"
elif targetIsFullHealth(enemy):
enemyState = "alive"
else:
enemyState = "undefined"
Event().wait(1.0 / 60)
print("Thread1 :", enemyState)
if cv.waitKey(1) == ord("-"):
cv.destroyAllWindows()
toggle = False
break
def macros(threadname):
while toggle:
Event().wait(1.0)
print("Thread2 :", enemyState)
if __name__ == "__main__":
print("test")
# WindowCapture.list_window_names();
thread1 = Thread(target=updateEnemyState, args=("Thread-1",))
thread2 = Thread(target=macros, args=("Thread-1",))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
inside utils.py
import numpy as np
import win32gui, win32ui, win32con
class WindowCapture:
# properties
w = 0
h = 0
hwnd = None
cropped_x = 0
cropped_y = 0
offset_x = 0
offset_y = 0
# constructor
def __init__(self, window_name=None):
# find the handle for the window we want to capture
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise Exception('Window not found: {}'.format(window_name))
# get the window size
window_rect = win32gui.GetWindowRect(self.hwnd)
self.w = window_rect[2] - window_rect[0]
self.h = window_rect[3] - window_rect[1]
# account for the window border and titlebar and cut them off
#border_pixels = 8
#titlebar_pixels = 30
border_pixels = 0
titlebar_pixels = 0
self.w = self.w - (border_pixels * 2)
self.h = self.h - titlebar_pixels - border_pixels
self.cropped_x = border_pixels
self.cropped_y = titlebar_pixels
# set the cropped coordinates offset so we can translate screenshot
# images into actual screen positions
self.offset_x = window_rect[0] + self.cropped_x
self.offset_y = window_rect[1] + self.cropped_y
def get_screenshot(self,startX=None,startY=None,width=None,height=None):
if startX is None : startX = self.cropped_x
if startY is None : startY = self.cropped_y
if width is None : width = self.w
if height is None : height = self.h
# get the window image data
wDC = win32gui.GetWindowDC(self.hwnd)
dcObj = win32ui.CreateDCFromHandle(wDC)
cDC = dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, width, height)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0, 0), (width, height), dcObj, (startX, startY), win32con.SRCCOPY)
# convert the raw data into a format opencv can read
#dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
signedIntsArray = dataBitMap.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (height, width, 4)
# free resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
img = img[...,:3]
img = np.ascontiguousarray(img)
return img
#staticmethod
def list_window_names():
def winEnumHandler(hwnd, ctx):
if win32gui.IsWindowVisible(hwnd):
print(hex(hwnd), win32gui.GetWindowText(hwnd))
win32gui.EnumWindows(winEnumHandler, None)
# translate a pixel position on a screenshot image to a pixel position on the screen.
# pos = (x, y)
# WARNING: if you move the window being captured after execution is started, this will
# return incorrect coordinates because the window position is only calculated in
# the __init__ constructor.
def get_screen_position(self, pos):
return (pos[0] + self.offset_x, pos[1] + self.offset_y)
I have provided all the code one needs to recreate an example, although the most important parts are hardcoded hence it is a pixel detector, you might need to use another window name and another pixel position and color values
The initial and first state change:
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread2 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Debug info
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread2 : alive
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread2's enemyState should have been dead
My guess is that enemyState remains locked inside thread2 for some reason.
The global statement has to be in each function that changes the value of toogle. You can't just include it once. Move that statement into updateEnemyState and it should start working. The macros function doesn't need that, because it doesn't change the value. And you might fix the spelling; it should be toggle.
After some trial and error, I have found out that time.sleep() is blocking the thread, used Event().wait(1.0) from Threading also instead of queue implementation I have decided that global variables would be best for simplicity's sake.
Related
Im trying to turn my screen capture into a thread. But I dont understand the error, new to threads. Also any idea why I get a random still image only in return or blank screen when I initialize WindowCapture() with a window name.
main.py
wincap = WindowCapture()
wincap.start()
windowcapture.py
import numpy as np
import win32gui, win32ui, win32con
from threading import Thread, Lock
class WindowCapture:
# threading properties
stopped = True
lock = None
screenshot = None
# properties
w = 0
h = 0
hwnd = None
cropped_x = 0
cropped_y = 0
offset_x = 0
offset_y = 0
# constructor
def __init__(self, window_name=None):
# create a thread lock object
self.lock = Lock()
# find the handle for the window we want to capture.
# if no window name is given, capture the entire screen
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise Exception('Window not found: {}'.format(window_name))
# get the window size
window_rect = win32gui.GetWindowRect(self.hwnd)
self.w = window_rect[2] - window_rect[0]
self.h = window_rect[3] - window_rect[1]
# account for the window border and titlebar and cut them off
border_pixels = 8
titlebar_pixels = 30
self.w = self.w - (border_pixels * 2)
self.h = self.h - titlebar_pixels - border_pixels
self.cropped_x = border_pixels
self.cropped_y = titlebar_pixels
# set the cropped coordinates offset so we can translate screenshot
# images into actual screen positions
self.offset_x = window_rect[0] + self.cropped_x
self.offset_y = window_rect[1] + self.cropped_y
def get_screenshot(self):
# get the window image data
wDC = win32gui.GetWindowDC(self.hwnd)
dcObj = win32ui.CreateDCFromHandle(wDC)
cDC = dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY)
# convert the raw data into a format opencv can read
#dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
signedIntsArray = dataBitMap.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (self.h, self.w, 4)
# free resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
# drop the alpha channel, or cv.matchTemplate() will throw an error like:
# error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type()
# && _img.dims() <= 2 in function 'cv::matchTemplate'
img = img[...,:3]
# make image C_CONTIGUOUS to avoid errors that look like:
# File ... in draw_rectangles
# TypeError: an integer is required (got type tuple)
# see the discussion here:
# https://github.com/opencv/opencv/issues/14866#issuecomment-580207109
img = np.ascontiguousarray(img)
return img
# find the name of the window you're interested in.
# once you have it, update window_capture()
# https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window
#staticmethod
def list_window_names():
def winEnumHandler(hwnd, ctx):
if win32gui.IsWindowVisible(hwnd):
print(hex(hwnd), win32gui.GetWindowText(hwnd))
win32gui.EnumWindows(winEnumHandler, None)
# translate a pixel position on a screenshot image to a pixel position on the screen.
# pos = (x, y)
# WARNING: if you move the window being captured after execution is started, this will
# return incorrect coordinates, because the window position is only calculated in
# the __init__ constructor.
def get_screen_position(self, pos):
return (pos[0] + self.offset_x, pos[1] + self.offset_y)
# threading methods
def start(self):
self.stopped = False
t = Thread(target=self.run)
t.start()
def stop(self):
self.stopped = True
def run(self):
# TODO: you can write your own time/iterations calculation to determine how fast this is
while not self.stopped:
# get an updated image of the game
screenshot = self.get_screenshot()
# lock the thread while updating the results
self.lock.acquire()
self.screenshot = screenshot
self.lock.release()
I have the tkinter GUI running on main thread and a while loop running on a second thread which updates label contents to be displayed on the GUI for a duration on n seconds. This runs fine on its own.
Now I want a << and >> button on the GUI which makes the loop:
pause the current iteration, call the display method with the previous item, and resume the iteration when done
skip the current item midway and move over to the next item.
But I could not find how to generate loop interrupt events. I cannot call continue or sleep depending on some variables/db entries which the buttons make (like in here Python: Tkinter pausing a while loop) because the interrupt can happen anywhere in the loop (not at a particular if/else line)
I have already referred to the Viewer program by codemy.com - https://github.com/flatplanet/Intro-To-TKinter-Youtube-Course/blob/master/viewer.py and of course google.
Here is a snippet of what I am trying to do. (https://gitlab.com/ananya26nov/PixelescoPy)
The main thread runs the GUI
guiObj = GuiWindow()
thread_list = []
thread = threading.Thread(target=worker, args=(guiObj,))
thread_list.append(thread)
thread.start()
guiObj.run_window_on_loop()
thread.join()
guiObj.quit_window()
The worker method for the thread has this:
while len(files_not_viewed) != 0:
chosen = random.choice(files_not_viewed)
if is_not_viewed(chosen):
pictureObj = Picture(chosen)
# Display the chosen for <timer> seconds
pictureObj.display(guiObj)
time.sleep(timer)
# Change the status of "chosen" to "viewed"
mark_as_viewed(chosen)
files_not_viewed = list(set(files_not_viewed) - set([chosen]))
The display method calls 'add_input_section' method of the following class
class GuiWindow():
def __init__(self):
self.root = Tk()
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
def run_window_on_loop(self):
button_exit = Button(self.root, text="Exit Program", command=self.root.quit)
button_exit.grid(row=1, column=1)
self.root.mainloop()
def quit_window(self):
self.root.quit()
def resize(self, image_path):
my_pic = Image.open(image_path)
pic_height = my_pic.height
pic_width = my_pic.width
if my_pic.height > (self.screen_height - 100):
new_height= self.screen_height - 100
new_width = int(new_height / pic_height * pic_width)
pic_height = new_height
pic_width = new_width
if pic_width > self.screen_width - 5:
new_width = self.screen_height - 5
new_height = int(new_width / pic_width * pic_height)
pic_height = new_height
pic_width = new_width
resized_image = my_pic.resize((pic_width, pic_height), Image.ANTIALIAS)
return resized_image
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
def remove_image(self):
self.image_label.grid_forget()
Use tk.BooleanVar() to manipulate the variables and also keep track the state when you try to pause so that you can resume when it's finished.
Problem: I want the code to exit the loop once a rectangle is drawn but it's not working! I have a loop with cv.waitKey() waiting to be pressed. How can I exit the loop from some external initiator? I am building off of someone else's code btw.
I tried using pynput but for some reason it isn't working... Here is the code and I would like to exit the while loop once the rectangle is drawn on the image.
import numpy as np
import cv2 as cv
from pynput.keyboard import Key, Controller
from time import sleep
class Interface:
def __init__(self, image):
self.ix = 0
self.iy = 0
self.ix2 = 0
self.iy2 = 0
self.drawing = False
self.before_rect = image.copy()
self.image = image.copy()
self.result_img = image.copy()
# Adding Function Attached To Mouse Callback
def draw(self,event,x,y,flags,params):
# Left Mouse Button Down Pressed
if(event==1):
self.drawing = True
self.ix = x
self.iy = y
# Moving mouse
if(event==0):
self.ix2 = x
self.iy2 = y
if self.drawing == True:
self.image = (self.before_rect).copy()
cv.rectangle(self.image, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
# Left Mouse Button Up
if(event==4):
if self.drawing == True:
self.ix2 = x
self.iy2 = y
# For Drawing Rectangle
cv.rectangle(self.result_img, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
self.image = (self.result_img).copy()
'''Here is the issue!!! How do I solve it?'''
# Doesn't do anything to exit the loop like I wanted :(
print('pressed') # (output to terminal) -> pressed
keyboard = Controller()
keyboard.press(Key.esc)
sleep(.01)
keyboard.release(Key.esc)
self.drawing = False
def get_boxes(self):
cv.namedWindow("Window")
# Adding Mouse CallBack Event
cv.setMouseCallback("Window", self.draw)
# Starting The Loop So Image Can Be Shown
while (True):
cv.imshow("Window", self.image)
if cv.waitKey(5) & 0xFF == 27:
break
cv.destroyAllWindows()
return
# Making The Blank Image
orig = np.zeros((512,512,3))
# Start drawing boxes!
obj = Interface(orig)
obj.get_boxes()
-it is executing the pynput keypress (I know since it prints 'pressed'), but it isn't producing any effect on the code. sad.
Thank you for the helppppp! ~(^ \_`_/ ^)~
Use boolean flags
Instead of implicitly breaking the loop through key commands, I think it is better and more Pythonic to use flags, i.e. boolean variables that tell you when to run or to break a loop. This is especially easy for you since you are working with an instantiated object, that is an instance that shares the same scope among its methods (through the self parameter).
For example, in your __init__() function, you can initialize a variable self.loop_running = True, which is defined upon instantiation.
Subsequently, in your draw() method, set this flag to False whenever you want to trigger a loop break, example:
def drawing(*args):
# some code
if self.drawing:
self.ix2 = x
self.iy2 = y
# For Drawing Rectangle
cv.rectangle(self.result_img, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
self.image = (self.result_img).copy()
# set flag to false ->
self.loop_running = False
Then in your final function where you call the main loop, change while True to while self.loop_running instead!:
def get_boxes(self):
cv.namedWindow("Window")
# Adding Mouse CallBack Event
cv.setMouseCallback("Window", self.draw)
# Starting The Loop So Image Can Be Shown
while self.loop_running:
cv.imshow("Window", self.image)
cv.destroyAllWindows()
return
I've created a class to display my webcam video on a Tkinter screen and I would like to take 3 pictures(waiting 3 seconds after each picture taken) after a Tkinter button is pressed.
Here is my code(reduced), and my logic to take picture is done. Should I use Threads to solve this? I'm new to Python.
import tkinter, cv2, time, dlib, numpy as np, time, threading
from PIL import Image, ImageTk
class Tela:
def __init__(self, janela):
self.janela = janela
self.janela.title("Reconhecimento Facial")
self.janela.config(background="#FFFFFF")
self.image = None
self.cam = cv2.VideoCapture(0)
self.detector = dlib.get_frontal_face_detector()
self.delay = 15
self.update()
self.janela.mainloop()
def update(self): # display image on gui
ret, frame = self.cam.read()
if ret:
faces, confianca, idx = self.detector.run(frame)
for i, face in enumerate(faces):
e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
cv2.rectangle(frame, (e, t), (d, b), (0, 255, 255), 2)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
self.image = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=self.image)
self.painel.imgtk = imgtk
self.painel.config(image=imgtk)
self.janela.after(self.delay, self.update)
def take_picture(self):
cou = 1 # counter of pictures
start = time.clock() # starts the time
ret, frame = self.cam.read()
if ret:
faces, confianca, idx = self.detector.run(frame)
secs = (time.clock() - start) # count seconds
for i, face in enumerate(faces):
e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
cv2.rectangle(frame, (e, t), (d, b), (0, 255, 255), 2)
if secs > 3:
imgfinal = cv2.resize(frame, (750, 600))
cv2.imwrite("fotos/pessoa." + str(id[0][0]) + "." + str(cou) + ".jpg", imgfinal)
print("Foto " + str(cou) + " tirada")
cou += 1
start = time.clock() # reset the counter of seconds
if cou > 3:
# here is where the thread should stop
# Creates the window
Tela(tkinter.Tk())
using time.sleep() will freeze your gui, with tkinter you can use after() which will call your method after x seconds, below is an example of how to call a function 4 times every 2 seconds, and you can use this idea in your application
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="Anything")
self.label.pack()
self.counter = 0
self.take_picture(repeates=4, seconds=2) # our desired function
self.root.mainloop()
def take_picture(self, repeates=0, seconds=1):
if repeates:
self.counter = repeates
if self.counter == 0:
print('no more execution')
self.label.configure(text='Done, no more execution')
return
# doing stuff
text = f'function counting down # {self.counter}'
self.label.configure(text=text)
# schedule another call to this func using after()
self.root.after(seconds * 1000, self.take_picture, 0, seconds)
self.counter -= 1 # our tracker
app=App()
credits to this answer
You should use time.sleep(). It takes an integer as an parameter and waits that many seconds and then the code resumes running.
I am looking to make the background of the tkinter canvas transparent, but still have Mouse events of the canvas, here is my code, I am on Windows 10, Python 3.6:
from tkinter import *
import time
WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
global old
old = ()
tk = Tk()
tk.title('Virtual whiteboard')
tk.wm_attributes('-transparentcolor', TRANSCOLOUR)
canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
def buttonmotion(evt):
global old
if old == ():
old = (evt.x, evt.y)
return
else:
canvas.create_line(old[0], old[1], evt.x, evt.y, width=LINEWIDTH)
old = (evt.x, evt.y)
def buttonclick(evt):
global old
canvas.create_line(evt.x-1, evt.y-1, evt.x, evt.y, width=LINEWIDTH)
old = (evt.x, evt.y)
canvas.bind('<Button-1>', buttonmotion)
canvas.bind('<B1-Motion>', buttonclick)
while True:
tk.update()
time.sleep(0.01)
When run the code, it makes a transparent background, but I select the things under, instead of the canvas.
I build a little workaround with the help of the win api, here is my suggestion:
from tkinter import *
import time
import win32gui
import win32api
WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))
Shot explanation of the new code bits:
tk.lift()
tk.wm_attributes("-topmost", True)
...
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
...
tk.bind('<Visibility>', putOnTop)
tk.focus()
These lines ensure, that the window will be always be on top of all other windows.
global HWND_t
HWND_t = 0
...
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
This code bit will go through all the windows currently displayed and catches the handle of the whiteboard window (make sure the title is unique, or this could capture the wrong handle).
state_left = win32api.GetKeyState(0x01)
...
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
This
Checks, if the handle is found
Checks, if mouse button 1 is clicked or not
Checks, if mouse is inside the window
if all is true, it takes the mouse data and draws the line
the current mode is, that it doesn't draw anything till the button is clicked and then draws until the button is clicked again.
I'm sure you've thought of this, but have you tried setting the hexadecimal colour as ""?
i.e
canvas = tk.Canvas(width, height, bg = "")
That works for my version of python.