Search code examples
pythontkintertimerwindowfocus

How to get inactivity timer to work in tkniter when tkinter window is not active?


I'm having trouble getting a timer (loop or anything) running when the tkinter window is withdrawn or not on top of other windows. The functionality of this is like a small screen saver, start the program, have icon displayed, click show the full screen image appears, if triple click it disappears and goes back to system tray. All of that works, however the timer in the after method only works if the window is displayed, and I want it to work when it's not displayed so it can display it after a period of inactivity (10 secs for testing).

How can I get the inactivity timer working when the tkinter window isn't displayed?

from tkinter import *
from ctypes import Structure, windll, c_uint, sizeof, byref
import time
import pystray
from PIL import Image, ImageTk

class LASTINPUTINFO(Structure):
    _fields_ = [
        ('cbSize', c_uint),
        ('dwTime', c_uint),
    ]

def get_idle_duration():
    lastInputInfo = LASTINPUTINFO()
    lastInputInfo.cbSize = sizeof(lastInputInfo)
    windll.user32.GetLastInputInfo(byref(lastInputInfo))
    millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
    return millis / 1000.0

class FS_Display:
    def __init__(self):
        self.root = Tk()
        self.root.title("FS Display")
        self.root.bind("<Triple-1>", self.end_fullscreen)
        self.root.bind("<Escape>", self.end_fullscreen)
        self.root.protocol('WM_DELETE_WINDOW', self.end_fullscreen)
        self.end_fullscreen()

    def end_fullscreen(self, event=None):
        self.root.withdraw()
        self.image = Image.open("favicon.png")
        self.menu = (pystray.MenuItem('Quit', self.quit), pystray.MenuItem('Show', self.show_fullscreen))
        self.icon = pystray.Icon("name", self.image, "FS Display", self.menu)
        self.icon.run()

    def show_fullscreen(self, event=None):
        self.root.attributes("-fullscreen", True)
        self.icon.stop()
        self.root.deiconify()

    def quit(self, event=None):
        self.icon.stop()
        self.root.destroy()

    def idle_check(self, event=None):
        GetLastInputInfo = int(get_idle_duration())
        print(GetLastInputInfo)
        if GetLastInputInfo == 10:
            self.show_fullscreen()
        self.root.after(1000, self.idle_check)

if __name__ == '__main__':
    fs = FS_Display()
    main_image = Image.open("D:black_background.png")
    photo = ImageTk.PhotoImage(main_image)
    Label(fs.root, image=photo).pack()
    fs.root.after(1000, fs.idle_check)
    fs.root.mainloop()

Solution

  • The timer and after code actually do work. The psytray code icon.run() blocks until icon.stop(). You can give icon.run() a callback that will allow it to run a timer. The downside is the icon menu options don't work during a callback loop.