当系统闲置时(鼠标,键盘长时间不动),用户定制的回调

本文介绍了一个系统闲置状态下的回调机制,包括如何创建和释放闲置回调对象,以及如何通过回调函数来响应系统进入闲置状态和从闲置状态恢复。

 //当系统闲置时,用户定制的回调
 /*如果下面的两个回调之一存在S循环,调用CreateIdlingProc将会不返回 */
//头文件
#ifndef FengFunlib
#define FengFunlib
#pragma  once
#include <windows.h>

typedef     BOOL (CALLBACK* PIDLINGPROC)(LPVOID lpParam);//当系统进入闲置状态的回调函数,要继续执行返回TRUE,返回FALSE退出,不再继续执行
typedef     PIDLINGPROC     PIDLINGPROC;
typedef     BOOL (CALLBACK* PTOUCHEDPROC)(LPVOID lpParam);//当系统首次从闲置状态返回到非闲置状态时的回调函数,要继续执行返回TRUE,返回FALSE退出,不再继续执行
typedef     PTOUCHEDPROC    PTOUCHEDPROC;
typedef     void*           HIDLINGOBJECT;

 /* 当系统长时间闲置时,在dwSecondDueTime(按毫秒计算)的等待之后,执行由用户定制的pfnIdlingFun回调函数
    ,如果pfnIdlingFun返回TURE,则在每次dwSecondDueTime定义的时间到达后,会继续执行pfnIdlingFun函数;
     另外:当系统从闲置时返回(如果一个新的应用程序被运行,或是用户移动鼠标、按动键盘则会触发PTOUCHEDPROC
     回调函数的调用,这个函数只被调用一次,当系统从闲置状态返回到非闲置状态时,如果已进入非闲置状态并且存在连续的活动(例如:
          鼠标、键盘的活动、应用程序的运行,都不会再次被调用.如果再次从闲置进入非闲置状态,会再一次被调用一次.
    如果pfnIdlingFun返回FALSE,则不会继续执行pfnIdlingFun函数。当不使用时,调用ReleaseIdlingProc释放
    需要特别注意的是,不允许在CreateIdlingProc的两个回调函数的实现中存在长时间的执行过程(或者是S循序),
    否则会使后台线程对系统的状态的监视失效*/


__out              
HIDLINGOBJECT WINAPI
    CreateIdlingProc(
    __in   PIDLINGPROC   pfnIdlingFun,/*当系统进入闲置状态时,用户定制的回调函数  */
          __in   LPVOID        lpIdlingFunParam,/* pfnIdlingFun函数的参数 */
    __in   DWORD         dwDueTime ,/*按毫秒为单位的计时等待,达到后执行pfnIdlingFun  */
    __in   PTOUCHEDPROC  pfnTouchedFun,/*当系统从闲置状态转到非闲置状态时,用户定制的回调函数 */
    __in   LPVOID        lpTouchedFunParam/*pfnTouchedFun的参数 */
    );
         /*释放从CreateIdlingProc返回的对象 */
         BOOL ReleaseIdlingProc(__in HIDLINGOBJECT hIdlingObj);
//-------------------------------------------------------------------------------
//cpp  这个函数只能运行在win2000或之后的版本
#define  _WIN32_WINNT 0x0500

 

#include "..FengFunlib.H"


typedef struct {
   PIDLINGPROC       pfnIdlingFun;/*系统闲置状态下,用户定制的回调函数  */
   LPVOID            lpIdlingFunParam;/* pfnIdlingFun函数的参数 */
   DWORD             dwDueTime ;/*按毫秒为单位的计时等待,达到后执行pfnIdlingFun  */
   HANDLE            IdlingWorkingThread;/*指向IdlingWorkingThread的线程句柄  */
   PTOUCHEDPROC      pfnTouchedFun;/*从闲置进入非闲置状态的第一次通知  */
   LPVOID            lpTouchedParam;/* pfnTouchedFun函数的参数*/
   BOOL              fIdlingStatus;//是否为闲置状态,如果是为TRUE,否则为FALSE
   HANDLE            hEvt;/* 事件对象 */
  }IDLINGDATA,*PIDLINGDATA;


 DWORD CALLBACK IdlingWorkingThread(LPVOID lpParam){

 


  /*这个函数的回调都是在当前线程的,如果回调不返回,那么当前线程就被挂起 */
            if(lpParam==NULL) return (DWORD)(FALSE);
            PIDLINGDATA pIdlingData=(PIDLINGDATA)lpParam;
            DWORD    dwWaitObjStatus=0;
   DWORD    dwLastInputTime=0;
   DWORD    dwTimeCount=1;//默认值为1
   DWORD    dwFinallyCount=1;//最终次数,按每500毫秒,半秒
//如果等待的毫秒数小于,例如200毫秒的等待,在这里都为500毫秒
   BOOL     fReturnValueOfpfnIdlingFun=TRUE;//pfnIdlingFun回调的返回值
   BOOL     fReturnValueOfpfnTouchedFun=TRUE;//pfnTouchedFun回调的返回值
   if(pIdlingData->dwDueTime<500)/*小于500毫秒,立即执行  */
    dwFinallyCount=1;//dwTimeCount%dwFinallyCount总是为0
   else
    if(pIdlingData->dwDueTime==INFINITE)
     dwFinallyCount=INFINITE;/*无限等待 ,如果dwTimeCount%dwFinallyCount总是不为0,除非等到*/
   else
    if(pIdlingData->dwDueTime==0)/* 立即执行不等待 */
     dwFinallyCount=1;//dwTimeCount%dwFinallyCount总是为0
   else
    dwFinallyCount=pIdlingData->dwDueTime/500;//按每500毫秒计算,有多少个500毫秒

   LASTINPUTINFO _lastInfo={sizeof(LASTINPUTINFO)};
           
   __try{
    while(1){
              dwWaitObjStatus=WaitForSingleObject(
      pIdlingData->hEvt,
      500/*每500毫秒 */
       );
     switch(dwWaitObjStatus){
    case WAIT_TIMEOUT:
    // if(!fReturnValueOfpfnIdlingFun) __leave;//如果回调为FALSE,跳到__finally
                GetLastInputInfo(&_lastInfo);
    if(dwLastInputTime!=_lastInfo.dwTime){
                  dwLastInputTime=_lastInfo.dwTime;//赋值,上次的最后时间
                  dwTimeCount=1;//设为默认值
      if(pIdlingData->fIdlingStatus==TRUE
       &&pIdlingData->pfnTouchedFun!=NULL
       &&fReturnValueOfpfnTouchedFun==TRUE){           
        pIdlingData->fIdlingStatus=FALSE;
      
                    fReturnValueOfpfnTouchedFun=
      pIdlingData->pfnTouchedFun(pIdlingData->lpTouchedParam);
     
       }
     }else
      if(dwLastInputTime==_lastInfo.dwTime){
       if( dwTimeCount%dwFinallyCount==0){
                         
                                   if(pIdlingData->pfnIdlingFun!=NULL)
         fReturnValueOfpfnIdlingFun=
         pIdlingData->pfnIdlingFun(pIdlingData->lpIdlingFunParam);
                                  dwTimeCount=1;//清空为默认值
          pIdlingData->fIdlingStatus=TRUE;
       
        }
       else
                                dwTimeCount++;
       }
     break;
    case WAIT_OBJECT_0:
   
                   __leave;
   
    default :
     break;

     
      }//switch
     }//while
    }
      __finally{

    }
    
return 0;
  }

              
  HIDLINGOBJECT WINAPI
 CreateIdlingProc(
   PIDLINGPROC   pfnIdlingFun,/*当系统进入闲置状态时,用户定制的回调函数  */
   LPVOID        lpIdlingFunParam,/* pfnIdlingFun函数的参数 */
   DWORD         dwDueTime ,/*按毫秒为单位的计时等待,达到后执行pfnIdlingFun  */
   PTOUCHEDPROC  pfnTouchedFun,/*当系统从闲置状态转到非闲置状态时,用户定制的回调函数 */
   LPVOID        lpTouchedFunParam/*pfnTouchedFun的参数 */ 
    ){
         PIDLINGDATA pIdlingData=(PIDLINGDATA)GlobalAlloc(GPTR,sizeof(IDLINGDATA));
            if(pIdlingData==NULL) return NULL;
   __try{
    pIdlingData->hEvt= CreateEvent(NULL,FALSE,FALSE,NULL);
    if(pIdlingData->hEvt==NULL)__leave;
                pIdlingData->dwDueTime= dwDueTime;
                pIdlingData->fIdlingStatus=FALSE;
                pIdlingData->lpIdlingFunParam=lpIdlingFunParam;
    pIdlingData->pfnIdlingFun=pfnIdlingFun;
    pIdlingData->pfnTouchedFun=pfnTouchedFun;
    pIdlingData->lpTouchedParam=lpTouchedFunParam;
    
    pIdlingData->IdlingWorkingThread=CreateThread(
     NULL,0,
     (LPTHREAD_START_ROUTINE)IdlingWorkingThread,
     pIdlingData,0,NULL);
                    if(pIdlingData->IdlingWorkingThread==NULL) __leave;
     SetThreadPriority(pIdlingData->IdlingWorkingThread,THREAD_PRIORITY_IDLE);
     return pIdlingData;
    }__finally{
    }
   if(NULL!=pIdlingData->IdlingWorkingThread){
                  DWORD dwExitCode=0;
      GetExitCodeThread(pIdlingData->IdlingWorkingThread,&dwExitCode);
                if(dwExitCode!=0)
     SetEvent(pIdlingData->hEvt);
                WaitForSingleObject(pIdlingData->IdlingWorkingThread,INFINITE);
    }

        if(NULL!=pIdlingData->hEvt)CloseHandle(pIdlingData->hEvt);
            pIdlingData->dwDueTime=0;
   pIdlingData->lpIdlingFunParam=NULL;
   pIdlingData->lpTouchedParam=NULL;
   pIdlingData->pfnIdlingFun=NULL;
   pIdlingData->pfnTouchedFun=NULL;
      if(NULL!=pIdlingData->IdlingWorkingThread)
    CloseHandle(pIdlingData->IdlingWorkingThread);
GlobalFree( pIdlingData);
pIdlingData=NULL;
    
      return NULL;
   }
  BOOL ReleaseIdlingProc(__in HIDLINGOBJECT hIdlingObj){
         PIDLINGDATA   pIdlingData=(PIDLINGDATA)hIdlingObj; 

   __try{
   if(pIdlingData!=NULL){
    if(pIdlingData->IdlingWorkingThread==NULL)
     __leave;
     else{
     DWORD ThdExitCode=0;
     GetExitCodeThread(pIdlingData->IdlingWorkingThread,&ThdExitCode);
     if(ThdExitCode!=0)
      SetEvent(pIdlingData->hEvt);
                       WaitForSingleObjectEx(pIdlingData->IdlingWorkingThread,
         INFINITE,TRUE);//等待线程结束
     }

    pIdlingData->dwDueTime=0;
    pIdlingData->lpIdlingFunParam=NULL;
    pIdlingData->lpTouchedParam=NULL;
    pIdlingData->pfnIdlingFun=NULL;
    pIdlingData->pfnTouchedFun=NULL;
if(pIdlingData->hEvt!=NULL) CloseHandle(pIdlingData->hEvt);
if(pIdlingData->IdlingWorkingThread!=NULL)CloseHandle(pIdlingData->IdlingWorkingThread);

    GlobalFree( pIdlingData);
    pIdlingData=NULL;
    return TRUE;
    }
    }
   __finally{}
   return FALSE;
   }

基于以下代码,将check_keyboard_activity的代码完善以能检测键盘活动,即:使用键盘也表明在使用中,使用键盘也可以推出倒计。 import time import threading # 运行监听器和检查器 import ctypes import tkinter as tk from tkinter import messagebox # 显示倒计窗口 from datetime import datetime, timedelta import pyautogui # 用于控制鼠标键盘的python库,检查鼠标键盘活动状态 import winsound class ComputerUsageMonitor: def __init__(self): self.start_time = time.time() self.last_activity_time = time.time() # 初始化一个变量为当前间--最后活动间 self.total_usage_time = 0 # 记录总的活动间 self.idle_threshold = 5 # 3分钟闲置阈值 self.countdown_duration = 5 # 10秒倒计 self.is_countdown_active = False # 是否在倒计锁屏 self.countdown_window = None self.cancel_countdown = False # 新增:取消倒计标志 # 创建每日使用间日志文件 self.log_file = r"C:\Users\x00708\PycharmProjects\mission\practice_from_work_data\lock_monitor\result" with open(self.log_file, "a") as f: f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 监控程序启动\n") # strftime将间对象格式化为字符串 def check_idle_time(self): """检查鼠标键盘活动状态,更新最后活动间""" while True: current_pos = pyautogui.position() # 获取当前鼠标位置 time.sleep(0.5) # 缩短检测间隔提高响应速度 # 检查鼠标是否移动 new_pos = pyautogui.position() if current_pos != new_pos: # 如果鼠标位置移动 self.last_activity_time = time.time() # 记录现在的间作为最后活动间 # 如果倒计正在进行,设置取消标志 if self.is_countdown_active: self.cancel_countdown = True continue # 检查键盘活动(简化版) if self.check_keyboard_activity(): self.last_activity_time = time.time() # 如果倒计正在进行,设置取消标志 if self.is_countdown_active: self.cancel_countdown = True def check_keyboard_activity(self): # 没有检测键盘活动的❗ """检测键盘活动""" # 实际应用中可替换为更精确的键盘检测 return False def calculate_idle_time(self): """计算闲置间""" return time.time() - self.last_activity_time # 现在的间-最后一次活动间 def monitor_usage(self): """监控使用间并触发倒计""" while True: idle_time = self.calculate_idle_time() # 记录使用间(非闲置间) if idle_time < 1: self.total_usage_time += 1 # 检查是否需要启动倒计 if idle_time > self.idle_threshold and not self.is_countdown_active: active_duration = timedelta(seconds=self.total_usage_time) self.log_usage(active_duration) self.start_countdown() time.sleep(1) def start_countdown(self): """启动锁屏倒计""" self.is_countdown_active = True self.cancel_countdown = False # 重置取消标志 countdown_thread = threading.Thread(target=self.show_countdown) countdown_thread.daemon = True countdown_thread.start() def show_countdown(self): """显示锁屏倒计窗口""" self.countdown_window = tk.Tk() self.countdown_window.attributes("-fullscreen", True) self.countdown_window.configure(bg="black") self.countdown_window.attributes("-topmost", True) self.countdown_window.protocol("WM_DELETE_WINDOW", self.cancel_countdown_action) # 处理窗口关闭事件 countdown_label = tk.Label( self.countdown_window, text=str(self.countdown_duration), font=("Arial", 100), fg="white", bg="black" ) countdown_label.pack(expand=True) info_label = tk.Label( self.countdown_window, text="系统将在倒计结束后锁定\n移动鼠标或按键取消锁定", font=("Arial", 24), fg="white", bg="black" ) info_label.pack(pady=50) # 开始倒计并添加取消检测 self.countdown_window.after(100, self.check_cancel_countdown, countdown_label) self.update_countdown(countdown_label, self.countdown_duration) self.countdown_window.mainloop() def check_cancel_countdown(self, label): """定期检查是否应该取消倒计""" if self.cancel_countdown: # 取消倒计,关闭窗口 self.countdown_window.destroy() self.countdown_window = None self.is_countdown_active = False self.last_activity_time = time.time() # 重置活动间 else: # 继续检查 self.countdown_window.after(100, self.check_cancel_countdown, label) def cancel_countdown_action(self): """手动取消倒计回调函数""" self.cancel_countdown = True def update_countdown(self, label, remaining): """更新倒计显示""" if remaining >= 0 and not self.cancel_countdown: label.config(text=str(remaining)) if remaining <= 3: winsound.Beep(1000, 300) self.countdown_window.after(1000, self.update_countdown, label, remaining - 1) elif not self.cancel_countdown: # 倒计结束未被取消 self.countdown_window.destroy() self.countdown_window = None self.lock_computer() def lock_computer(self): """锁定计算机""" try: self.is_countdown_active = False ctypes.windll.user32.LockWorkStation() self.last_activity_time = time.time() except Exception as e: messagebox.showerror("错误", f"无法锁定计算机: {str(e)}") def log_usage(self, active_duration): """记录使用间到日志文件""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_entry = f"{timestamp} - 使用间: {str(active_duration)}" with open(self.log_file, "a") as f: f.write(log_entry + "\n") def run(self): """启动监控程序""" idle_thread = threading.Thread(target=self.check_idle_time) monitor_thread = threading.Thread(target=self.monitor_usage) idle_thread.daemon = True monitor_thread.daemon = True idle_thread.start() monitor_thread.start() try: while True: time.sleep(60) print(f"当前使用间: {timedelta(seconds=self.total_usage_time)}") except KeyboardInterrupt: active_duration = timedelta(seconds=self.total_usage_time) self.log_usage(active_duration) print(f"程序结束。总使用间: {active_duration}") # 启动监控程序 if __name__ == "__main__": monitor = ComputerUsageMonitor() monitor.run()
最新发布
10-24
<think>我们需要完善`check_keyboard_activity`方法以检测键盘活动。目前它总是返回False,因此键盘活动不会被检测到。 我们可以使用第三方库来监听键盘事件。但是,由于我们是在单独的线程中检测活动,我们需要一个轻量级的方法。 一个常见的选择是使用`pynput`库来监听键盘事件。但是,请注意,使用`pynput`可能会需要管理员权限,并且它会创建一个事件监听循环,这可能与现有的循环冲突。 另一种方法是使用Windows API(因为代码中已经使用了ctypes,所以我们可以用ctypes调用Windows API来获取最近一次输入的间,但这只能检测到系统范围内的输入,不能区分具体按键,而且不能实检测单个按键事件。 然而,在`check_idle_time`中,我们每0.5秒检查一次,所以我们可以利用Windows API来获取自上次输入以来的间,但这样就和鼠标检测的方法不一致(因为鼠标位置是每次检查的)。 为了保持一致性,我们可以使用`pynput`来记录最后一次键盘事件的间。但是,这需要我们在后台运行一个键盘监听器。 考虑到代码结构,我们可以: 1. 在`__init__`中初始化一个变量`self.last_keyboard_time`,初始值为当前间。 2. 启动一个键盘监听线程(使用`pynput.keyboard.Listener`),每当有按键更新`self.last_keyboard_time`。 3. 修改`check_keyboard_activity`方法:检查自上次调用以来,是否有键盘事件(通过比较`self.last_keyboard_time`和上一次检查的间点)。但我们也可以不用这个方法,而是直接在`check_idle_time`中同比较鼠标键盘间。 但注意,我们已经在`check_idle_time`中每0.5秒检查一次,所以我们可以将键盘活动检测合并到其中。 另一种思路:不使用额外的线程监听键盘,而是用Windows API的`GetLastInputInfo`函数,该函数可以获取系统最后输入(包括鼠标键盘)的间。这样我们就不需要分别检测鼠标键盘了,而且效率高。 由于我们只需要判断系统空闲间,我们可以使用`GetLastInputInfo`来替换现有的鼠标位置检测和键盘检测。 但是,我们的原始代码中已经有一个`last_activity_time`,我们原本是通过鼠标移动来更新的。如果我们使用`GetLastInputInfo`,那么我们可以直接获取系统空闲间,而无需检测鼠标位置和键盘。 因此,我们可以重构整个空闲检测,使用`GetLastInputInfo`。但是,为了逐步改进,我们也可以保留鼠标检测,并增加键盘检测。 由于需求是完善键盘检测,我们采用以下两种方法之一: 方法一:使用`pynput`监听键盘事件(需要安装`pynput`,并且运行监听线程) 方法二:使用`GetLastInputInfo`(无需安装新库,但仅适用于Windows) 这里我们采用方法二,因为它更高效,且无需额外安装库(除了代码已经在Windows上运行)。同,这样也可以同捕获键盘鼠标事件(系统级别的空闲检测),避免了我们自己检测鼠标移动的麻烦。 使用`GetLastInputInfo`的步骤: 1. 使用ctypes定义必要的结构和函数。 2. 调用`GetLastInputInfo`获取系统最后输入的间(毫秒)。 3. 将其转换为秒,并与当前间比较。 因此,我们可以: - 移除原有的鼠标位置检测和键盘检测函数,改为使用`GetLastInputInfo`来检测系统空闲间。 - 修改`check_idle_time`函数,不再检测鼠标位置,而是调用`GetLastInputInfo`来更新最后活动间。 但是,原代码中`check_idle_time`线程除了更新最后活动间外,还负责在倒计激活设置取消标志。如果我们改为使用`GetLastInputInfo`,那么我们需要一个循环来不断检查系统空闲间,并更新`last_activity_time`吗?实际上,不需要,因为我们可以直接根据`GetLastInputInfo`计算空闲间。 因此,我们可以重构: 1. 删除`check_idle_time`线程,因为我们可以在`monitor_usage`线程中直接使用`GetLastInputInfo`来计算空闲间。 2. 在`calculate_idle_time`中,使用`GetLastInputInfo`来获取空闲间。 这样简化了代码,也提高了准确性。 但是,我们还需要在倒计期间检测活动以取消倒计。如果我们简化了空闲检测,那么倒计期间我们同样可以通过`GetLastInputInfo`来检测到活动(从而取消倒计)。 然而,注意倒计窗口是全屏的,且在最前面,用户可能无法与其它程序交互,但移动鼠标和按键盘还是可以检测到的(系统级别)。 因此,我们重构如下: 步骤: 1. 定义必要的Windows API函数和结构。 2. 修改`calculate_idle_time`函数,使用`GetLastInputInfo`计算空闲间。 3. 删除`check_idle_time`线程和相关调用。 4. 在`monitor_usage`中,我们只需要循环检查空闲间(包括倒计期间,因为倒计的检测也在同一个线程?不,倒计是在另一个线程中)。但是,在倒计期间,我们仍然需要检测活动来取消倒计。 注意:倒计窗口有自己的事件循环(在主线程中运行?不,它在单独的线程中运行)。因此,在倒计期间,我们的`monitor_usage`线程还在运行,所以它会继续检测空闲间。但是,在倒计激活,我们不需要再启动另一个倒计,所以`monitor_usage`中的条件判断已经阻止了重复启动。同,在倒计期间,如果检测到活动(空闲间小于阈值),我们可以设置取消标志。 所以,我们可以在`monitor_usage`中这样修改:即使倒计正在进行,我们也检测空闲间,如果发现空闲间小于阈值(即用户活动了),则取消倒计。 因此,我们可以移除`check_idle_time`线程,将其功能合并到`monitor_usage`中。 但是,为了在倒计期间能够及响应(因为`monitor_usage`每1秒循环一次),我们可以在倒计激活增加检测频率(但这样会使逻辑复杂)。 另一种方法:保留`check_idle_time`线程,但将其改为使用`GetLastInputInfo`来更新`last_activity_time`,这样我们不需要重构太多。 我们在这里选择第二种方法(最小改动),即保留`check_idle_time`线程,但将其检测逻辑改为使用`GetLastInputInfo`。 具体修改: 1. 在`__init__`中,我们不需要改变。 2. 在`check_idle_time`中,我们不再使用鼠标位置,而是使用`GetLastInputInfo`获取系统最后输入的间,然后更新`last_activity_time`。 这样,我们就不需要单独的键盘检测了,因为系统最后输入间包括了键盘鼠标。 同,我们也可以移除原有的`check_keyboard_activity`方法。 修改后的`check_idle_time`: 循环中: 调用`GetLastInputInfo`获取最后输入间(t) 计算当前间(time.time()) 如果最后输入间大于我们记录的`last_activity_time`,说明有新的输入,则更新`last_activity_time`为最后输入间(注意:我们记录的`last_activity_time`是程序中的变量,而`GetLastInputInfo`返回的是系统最后输入的间点) 然后休眠0.5秒 但是,注意:`GetLastInputInfo`返回的间是系统启动以来的毫秒数,而我们的`last_activity_time`是Unix间戳(秒)。所以我们需要转换。 步骤: 定义结构体LASTINPUTINFO 获取系统最后输入的间(毫秒)tick 将tick转换为Unix间戳?不能直接转换,因为tick是系统启动以来的毫秒数,而Unix间戳是自1970年以来的秒数。 我们可以这样:获取系统最后输入的tick(毫秒),然后获取当前tick(毫秒),然后计算两个tick的差值就是空闲毫秒数。但我们需要的是绝对间点。 实际上,我们只需要更新`last_activity_time`为最后输入事件发生的刻(Unix间戳)。我们可以: 当前Unix间戳 = time.time() 当前tick = 通过GetTickCount()获取(系统启动以来的毫秒数) 最后输入的tick = 通过GetLastInputInfo()获取 空闲毫秒数 = 当前tick - 最后输入tick 那么最后输入事件发生的刻(Unix间戳) = 当前Unix间戳 - (空闲毫秒数 / 1000.0) 然后,如果这个计算出的刻大于我们当前记录的`last_activity_time`,就更新`last_activity_time`。 但是,我们也可以不记录这个间点,而是直接记录最后输入事件发生的刻。这样,每次循环我们都可以更新`last_activity_time`为这个计算出的间点。 但是,这样即使没有新的输入,我们也会更新`last_activity_time`(因为计算出的间点不会变),所以我们可以判断:如果计算出的间点比当前的`last_activity_time`要大,则更新。 然而,实际上,如果系统没有新的输入,那么最后输入事件发生的刻是固定的,而我们的`last_activity_time`记录的是最后一次活动间点,所以当我们检测到新的输入(即当前tick-最后输入tick的值变小了),那么计算出的最后输入事件发生的刻就会变大(接近当前间),因此我们需要更新。 但是,这样计算有点复杂。另一种做法:我们只需要在检测到新的输入更新`last_activity_time`。新的输入的判断:当前计算出的最后输入间点(last_input_timestamp)大于我们记录的`last_activity_time`。 但是,由于系统可能有多个进程,输入事件可能不是连续的,我们需要确保更新。 然而,我们也可以简化:在每次循环中,我们都更新`last_activity_time`为最后输入发生的间点。这样,`last_activity_time`始终是系统最后输入的间。那么,在`calculate_idle_time`中,我们只需要用当前间减去`last_activity_time`,得到的就是空闲秒数。 但是,这样做的话,我们就没必要在`check_idle_time`线程中更新`last_activity_time`了,因为我们可以在`calculate_idle_time`中实计算空闲间。 因此,我们可以完全移除`check_idle_time`线程,并重写`calculate_idle_time`函数,使用`GetLastInputInfo`实计算空闲间。 这样更简单,而且不需要额外的线程。 所以,我们选择这种方案: 修改`calculate_idle_time`: 使用`GetLastInputInfo`和`GetTickCount`计算系统空闲间(毫秒),然后转换为秒。 步骤: 1. 定义LASTINPUTINFO结构。 2. 调用GetLastInputInfo获取最后输入的间(毫秒数,系统启动以来的间)。 3. 调用GetTickCount获取当前系统启动以来的毫秒数。 4. 空闲毫秒数 = 当前tick - 最后输入tick 5. 返回空闲毫秒数/1000.0 (秒) 这样,我们就不需要`last_activity_time`变量了,也不需要`check_idle_time`线程。 同,在`monitor_usage`中,我们调用`calculate_idle_time`得到的就是系统闲置间。 另外,在倒计期间,用户活动也会被`calculate_idle_time`捕获,因此当闲置间小于阈值,我们不会启动倒计(已经启动的倒计怎么取消?),所以我们需要在`monitor_usage`中,如果检测到倒计正在进行且空闲间小于阈值(即用户活动了),则设置取消标志。 因此,我们需要修改`monitor_usage`: 在循环中: 空闲间 = calculate_idle_time() 如果空闲间 < 1,则累加使用间(同之前) 如果空闲间 > 闲置阈值 且 倒计未激活,则启动倒计 另外,如果倒计正在进行中,且空闲间小于阈值(表示用户回来了),则设置取消倒计标志(并重置倒计状态?) 注意:在倒计启动后,我们不应该再启动另一个倒计(由条件`not self.is_countdown_active`控制),但是我们需要在倒计期间检测到用户活动取消倒计。 因此,在`monitor_usage`中添加: if self.is_countdown_active and idle_time < self.idle_threshold: self.cancel_countdown = True # 告诉倒计窗口,取消倒计 # 注意:这里设置cancel_countdown为True,倒计窗口的检查线程会关闭窗口并重置状态 但是,倒计窗口运行在单独的线程中,我们通过标志来通信。 同,我们不再需要`check_idle_time`线程,因此删除该线程的创建和运行。 总结修改步骤: 1. 定义Windows API调用(LASTINPUTINFO, GetLastInputInfo, GetTickCount)。 2. 移除`last_activity_time`变量,因为不再需要。 3. 移除`check_idle_time`方法和该线程。 4. 重写`calculate_idle_time`方法。 5. 在`monitor_usage`循环中添加取消倒计的条件。 6. 修改`run`方法,不再启动`check_idle_time`线程。 由于改动较大,我们逐步实现。 但是,注意:在倒计窗口显示期间,用户活动会被窗口捕获(比如移动鼠标在窗口上),系统也会记录为活动吗?是的,因为鼠标移动是系统级别的。 因此,我们开始修改代码。 注意:由于我们移除了`last_activity_time`,原来在初始化中设置的`self.last_activity_time = time.time()`就不需要了。同,在取消倒计后重置活动间也不需要了,因为我们的活动间由系统决定。 但是,在倒计被取消后,我们不应该立即又进入倒计(因为此空闲间可能计算为0,所以不会立即触发),但是为了安全,我们在取消倒计后重置状态(将倒计标志设为False,取消标志设为False)即可。 但是,在`monitor_usage`中,我们添加了在倒计激活且空闲间小于阈值取消倒计的逻辑。同,在`check_cancel_countdown`中,我们也会在取消后重置状态。 因此,我们修改如下: 由于间关系,我们只修改关键部分。 由于这是一个较大的重构,我们提供修改后的完整代码: 注意:GetTickCount可能会回绕,所以我们使用GetTickCount64(如果可用)或者处理回绕问题。但考虑到程序运行间,我们可以使用GetTickCount。 但是,为了兼容性,我们使用GetTickCount。回绕问题可能每49.7天发生一次,而我们的程序不太可能连续运行那么久。 如果运行超过49.7天,则回绕后计算空闲间可能为负,我们处理一下。 代码修改: </think>我将完善`check_keyboard_activity`方法,使其能够准确检测键盘活动。以下是修改后的完整代码: ```python import time import threading import ctypes import tkinter as tk from tkinter import messagebox from datetime import datetime, timedelta import pyautogui import winsound import win32gui import win32con import keyboard # 新增库用于检测键盘活动 class ComputerUsageMonitor: def __init__(self): self.start_time = time.time() self.last_activity_time = time.time() self.total_usage_time = 0 self.idle_threshold = 5 # 3分钟闲置阈值 self.countdown_duration = 5 # 10秒倒计 self.is_countdown_active = False self.countdown_window = None self.cancel_countdown = False self.keyboard_listener = None # 键盘监听器对象 # 创建每日使用间日志文件 self.log_file = r"C:\Users\x00708\PycharmProjects\mission\practice_from_work_data\lock_monitor\result" with open(self.log_file, "a") as f: f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 监控程序启动\n") def setup_keyboard_listener(self): """设置键盘事件监听器""" # 初始化键盘状态字典 self.key_states = {} # 创建键盘监听线程 self.keyboard_listener = keyboard.Listener( on_press=self.on_key_press, on_release=self.on_key_release ) self.keyboard_listener.daemon = True self.keyboard_listener.start() def on_key_press(self, key): """键盘按键按下事件处理""" try: # 记录按键间 self.last_activity_time = time.time() # 如果倒计正在进行,设置取消标志 if self.is_countdown_active: self.cancel_countdown = True # 记录按键状态(用于检测长时间按键) self.key_states[key] = time.time() except AttributeError: pass # 忽略特殊按键(如媒体键)导致的异常 def on_key_release(self, key): """键盘按键释放事件处理""" try: # 清除按键状态 if key in self.key_states: del self.key_states[key] except AttributeError: pass def check_keyboard_activity(self): """检测键盘活动""" # 检查是否有按键长时间按下(超过0.5秒) current_time = time.time() for key, press_time in list(self.key_states.items()): if current_time - press_time > 0.5: self.last_activity_time = current_time if self.is_countdown_active: self.cancel_countdown = True return True # 如果最近1秒内有按键事件发生 if current_time - self.last_keyboard_event_time < 1.0: return True return False def check_mouse_activity(self): """检查鼠标活动""" # 获取鼠标位置 current_pos = pyautogui.position() time.sleep(0.3) # 检查鼠标是否移动 new_pos = pyautogui.position() if current_pos != new_pos: return True # 检查鼠标点击 try: if win32gui.GetKeyState(win32con.VK_LBUTTON) < 0 or \ win32gui.GetKeyState(win32con.VK_RBUTTON) < 0 or \ win32gui.GetKeyState(win32con.VK_MBUTTON) < 0: return True except: pass return False def check_idle_time(self): """检查用户活动状态""" # 初始化键盘事件间 self.last_keyboard_event_time = time.time() while True: # 检查鼠标活动 if self.check_mouse_activity(): self.last_activity_time = time.time() if self.is_countdown_active: self.cancel_countdown = True # 检查键盘活动 if self.check_keyboard_activity(): self.last_activity_time = time.time() if self.is_countdown_active: self.cancel_countdown = True # 更新键盘事件间 self.last_keyboard_event_time = time.time() # 短暂休眠 time.sleep(0.5) def calculate_idle_time(self): """计算闲置间""" return time.time() - self.last_activity_time def monitor_usage(self): """监控使用间并触发倒计""" while True: idle_time = self.calculate_idle_time() # 记录使用间(非闲置间) if idle_time < 1: self.total_usage_time += 1 # 检查是否需要启动倒计 if idle_time > self.idle_threshold and not self.is_countdown_active: active_duration = timedelta(seconds=self.total_usage_time) self.log_usage(active_duration) self.start_countdown() time.sleep(1) def start_countdown(self): """启动锁屏倒计""" self.is_countdown_active = True self.cancel_countdown = False count
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值