开发环境
- Windows
- Python 3
- 依赖库:pynput
需求背景
就像电脑无操作几分钟后会进入屏保/锁定屏幕一样,我希望在电脑一段时间无操作后执行某些命令(比如自动保存、最小化、隐藏窗口)。
设计方法
Python内建threading库的Timer类可以发起后台计时器,不过我希望在最后一个计时器结束计时的时候才执行任务,所以我设计了一个自己的计时器类:
添加计时器
这个计时器类可以根据接收的延时时间,发起很多计时器后台线程,并且增加一个线程就标记有一个线程正在运行(线程计数器加1)。
在每个线程运行结束后,判断当前是否还有未运行完毕的线程,如果没有,则执行预先设定的回调函数。
下面做了一个简单的例子,用tkinter绘制窗口,输入框输入数字(或小数)设置延时时间,(多次)点击按钮启动后台线程计时器。
注意每一次可以设置不同的延时时间。当最后一个计时器结束运行时,会执行回调函数:
示例代码
import tkinter
from threading import Timer
from threading import Thread
class MyTimer:
def __init__(self, func):
self.func = func
self.count = 0
def wait(self, seconds):
self.count += 1
Timer(seconds, self.timeout).start()
def timeout(self):
self.count -= 1
if self.count == 0:
self.func()
def callback():
timer.wait(float(txt.get()))
timer = MyTimer(lambda: print("It's time to work!"))
top = tkinter.Tk()
txt = tkinter.Entry(top)
txt.insert(0, '5')
txt.pack(side='left')
tkinter.Button(top, text='延时执行', command=callback).pack(side='left')
top.mainloop()
此方法不容易将后台线程全部安全退出。不过在主线程停止后,不会再发起新的线程,所以最后一个线程完成计时后后台线程就会停止。
但是为了解决立刻退出的问题,我又写了另一种计时器:只让最后一次设定的计时器有效。
唯一的区别是当每次延时如果设置了不同的时间参数,可能得到不同的执行顺序。不过这种需求或许并不经常出现:
class MyLastTimer:
def __init__(self, func):
self.func = func
self.th = Timer(0, int)
def wait(self, seconds): # only last timer work.
self.th.cancel()
self.th = Timer(seconds, self.timeout)
self.th.start()
def timeout(self):
self.func()
添加键盘监控
结合我最近写的另一篇文章:《Python开发基于pynput的后台鼠标键盘监控程序示例》
实现键盘监控器后,获得任何有关键盘/鼠标的操作,刷新延时计时器的线程。计时器截止后会运行传入的函数,即可实现指定时间内无操作后触发的任务操作。
监控器可以用monitor.stop()、计时器可以用timer.th.cancel()方法安全退出,防止主线程关闭后仍然会有线程执行任务。
完整代码
from threading import Timer
from threading import Thread
import pynput
class Monitor:
def __init__(self, func):
self.func = func
Thread(target=self.mouser).start()
Thread(target=self.keyboarder).start()
def hook(self, *typ_names):
for name in typ_names:
yield (lambda typ: (lambda *args: self.func(typ, *args)))(name)
def mouser(self):
with pynput.mouse.Listener(*self.hook('move', 'click', 'scroll')) as self.ml:
self.ml.join()
def keyboarder(self):
with pynput.keyboard.Listener(*self.hook('press', 'release')) as self.kl:
self.kl.join()
def close(self):
pynput.keyboard.Listener.stop(self.kl)
pynput.mouse.Listener.stop(self.ml)
class MyLastTimer:
def __init__(self, func):
self.func = func
self.th = Timer(0, int)
def wait(self, seconds): # only last timer work.
self.th.cancel()
self.th = Timer(seconds, self.timeout)
self.th.start()
def timeout(self):
self.func()
timer = MyLastTimer(lambda: print('No actions in 5 seconds!'))
Monitor(lambda *_: timer.wait(5))