Python Forum
[Tkinter] Background inactivity timer when tkinter window is not active
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Background inactivity timer when tkinter window is not active
#1
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()
Reply
#2
This works fine for me.
import tkinter as tk
from ctypes import Structure, windll, c_uint, sizeof, byref

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))
    count = windll.kernel32.GetTickCount()
    return (count - lastInputInfo.dwTime) / 1000.0

class Window(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.counter = tk.IntVar(self, 0)
        self.timer = tk.DoubleVar(self, 0.0)
        tk.Label(self, textvariable=self.counter, width=20).pack(padx=10, pady=10)
        tk.Button(self, text="Iconify", command=self.iconify).pack(padx=10, pady=10)
        self.update()

    def update(self):
        self.counter.set(self.counter.get() + 1)
        if get_idle_duration() > 10.0:
            print("Time to wake up!")
            self.deiconify()
        self.after(1000, self.update)

Window().mainloop()
It also works if the Iconify button calls withdraw() instead of iconify().
Reply
#3
(Apr-14-2022, 08:00 PM)deanhystad Wrote: This works fine for me.

Thank you for the sample. I guess it does work, however it appears that the pystray code is not allowing it to function properly. It blocks on icon.run() until icon.stop(). Thank you for the nudge in the right direction.
Reply
#4
From the pstray documentation. https://buildmedia.readthedocs.org/media...ystray.pdf

Quote:he call to pystray.Icon.run() is blocking, and it must be performed from the main thread of the application.

You will not be able to do what you want with ".after()" which requires mainloop() to process the event. I think you need a to run the idle check in a separate thread.
import tkinter as tk
import time
import threading
from ctypes import Structure, windll, c_uint, sizeof, byref

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))
    count = windll.kernel32.GetTickCount()
    return (count - lastInputInfo.dwTime) / 1000.0

def run_blocking_task():
    """Simulate icon.run()"""
    global running_blocking_task
    running_blocking_task = True
    while running_blocking_task:
        time.sleep(0.1)

def stop_blocking_task():
    """Simulate icon.stop()"""
    global running_blocking_task
    running_blocking_task = False

class Window(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.counter = tk.IntVar(self, 0)
        self.timer = tk.DoubleVar(self, 0.0)
        tk.Label(self, textvariable=self.counter, width=20).pack(padx=10, pady=10)
        tk.Button(self, text="Iconify", command=self.dock).pack(padx=10, pady=10)
        self.update()

    def dock(self):
        """This will block tkinter from running.  Start thread to run idle check"""
        self.iconify()
        self.idle_thread = threading.Thread(target=self.idle_check)
        self.idle_thread.start()
        print("Time to go to bed!")
        run_blocking_task()
        print("Time to wake up!")
        self.deiconify()

    def idle_check(self):
        while True:
            if get_idle_duration() > 10.0:
                stop_blocking_task()
                break
            else:
                time.sleep(0.1)

    def update(self):
        self.counter.set(self.counter.get() + 1)
        self.after(1000, self.update)

Window().mainloop()
Running the idle checker in a different thread let's that code run in parallel with the dock, but it does stop tkinter.mainloop(). Notice that the counter in the window does not increment while the window is hidden. Is that a problem?
DBox likes this post
Reply
#5
(Apr-15-2022, 06:38 PM)deanhystad Wrote: Running the idle checker in a different thread let's that code run in parallel with the dock, but it does stop tkinter.mainloop(). Notice that the counter in the window does not increment while the window is hidden. Is that a problem?

No, that's not a problem at all for what I'm trying to do. Thanks so much for this, I believe I can do what I need to now.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Interaction between Matplotlib window, Python prompt and TKinter window NorbertMoussy 3 489 Mar-17-2024, 09:37 AM
Last Post: deanhystad
  Transparent window background, but not text on it muzicman0 7 2,846 Feb-02-2024, 01:28 AM
Last Post: Joically
  Tkinter multiple windows in the same window tomro91 1 845 Oct-30-2023, 02:59 PM
Last Post: Larz60+
  Centering and adding a push button to a grid window, TKinter Edward_ 15 4,716 May-25-2023, 07:37 PM
Last Post: deanhystad
  My Background Image Is Not Appearing (Python Tkinter) HailyMary 2 4,234 Mar-14-2023, 06:13 PM
Last Post: deanhystad
  [Tkinter] Open tkinter colorchooser at toplevel (so I can select/focus on either window) tabreturn 4 1,900 Jul-06-2022, 01:03 PM
Last Post: deanhystad
  why my list changes to a string as I move to another window in tkinter? pymn 4 2,576 Feb-17-2022, 07:02 AM
Last Post: pymn
  [Tkinter] Not able to get image as background in a Toplevel window finndude 4 3,912 Jan-07-2022, 10:10 PM
Last Post: finndude
  [Tkinter] Tkinter Window Has no Title Bar gw1500se 4 2,842 Nov-07-2021, 05:14 PM
Last Post: gw1500se
  "tkinter.TclError: NULL main window" Rama02 1 5,843 Feb-04-2021, 06:45 PM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020