def reminder(message, *, interval: float = 20.0, daemon=False, lines: int = 2):
"""
Starts a thread that periodically takes screenshots.
If the screen hasn't changed between intervals, overlays a reminder
message and waits for user interaction before resuming.
"""
import threading
from PIL import ImageChops
import pygame
import ctypes
def images_equal(img1, img2, threshold=5):
diff = ImageChops.difference(img1.convert("L"), img2.convert("L"))
bbox = diff.getbbox()
if not bbox:
return True
stat = diff.crop(bbox).getextrema()
return stat[1] < threshold
def bring_to_front():
if sys.platform == "win32":
hwnd = pygame.display.get_wm_info()["window"]
ctypes.windll.user32.SetWindowPos(hwnd, -1, 0, 0, 0, 0, 1 | 2)
def get_screen_size():
import tkinter as tk
root = tk.Tk()
root.withdraw()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
root.destroy()
return (width, height)
def show_reminder(bg_img: Image.Image):
import tkinter as tk
from PIL import ImageTk
screen_size = get_screen_size()
reminder_img = render_text(message, size=screen_size, bg=bg_img, lines=lines)
root = tk.Tk()
root.title("Reminder")
root.attributes("-fullscreen", True)
root.configure(background='black')
root.focus_force()
img_tk = ImageTk.PhotoImage(reminder_img)
label = tk.Label(root, image=img_tk)
label.pack()
root.bind("<Key>", lambda e: root.destroy())
root.bind("<Button>", lambda e: root.destroy())
root.bind("<Motion>", lambda e: root.destroy())
root.mainloop()
def loop():
reminder_dir = gw.resource("work", "reminder")
os.makedirs(reminder_dir, exist_ok=True)
last_img = None
while True:
current = ImageGrab.grab()
current.save(os.path.join(reminder_dir, "next.png"))
if last_img and images_equal(current, last_img):
current.save(os.path.join(reminder_dir, "original.png"))
show_reminder(current)
last_img = current
time.sleep(float(interval))
thread = threading.Thread(target=loop, daemon=daemon)
thread.start()
if not daemon:
try:
while thread.is_alive():
thread.join(timeout=1)
except (KeyboardInterrupt, EOFError):
print("Exiting reminder.")
return
return thread