吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 265|回复: 11
上一主题 下一主题
收起左侧

[Python 原创] 摸鱼计时器

[复制链接]
跳转到指定楼层
楼主
Yc12138 发表于 2026-8-6 14:59 回帖奖励
今天突发奇想想看看自己一天能空闲(moyu)多久,所以就开发了个空闲计时小工具
基础逻辑就是实时监控自己电脑的鼠标和键盘,当启动小工具时就开始实时监控,当符合你设置的空闲市场后开始计时然后可以累计,还可以把每天的空闲时间都记录下来
本工具是基于Python进行开发,为了可以查询累计记录还用了轻量化数据库[color=var(--color-fg-default)]SQLite进行储存,如下为详细代码:
[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
# idle_timerv4.py
# 完整的空闲计时器应用 (Python 3.x)

import sqlite3
import time
import threading
import datetime
import ctypes
from ctypes import wintypes
import tkinter as tk
from tkinter import ttk

# --- 1. 数据库管理模块 ---
class DatabaseManager:
    def __init__(self, db_path='idle_tracker.db'):
        self.db_path = db_path
        # 使用 check_same_thread=False 允许跨线程使用,配合锁保证安全
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self.lock = threading.Lock()
        self.create_table()

    def create_table(self):
        with self.lock:
            cursor = self.conn.cursor()
            cursor.execute('''
            CREATE TABLE IF NOT EXISTS idle_sessions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                start_time TEXT NOT NULL,
                end_time TEXT NOT NULL,
                duration_seconds REAL NOT NULL,
                date TEXT NOT NULL
            )
            ''')
            self.conn.commit()

    def insert_record(self, start_time, end_time, duration):
        with self.lock:
            cursor = self.conn.cursor()
            date_str = start_time.strftime('%Y-%m-%d')
            cursor.execute('''
            INSERT INTO idle_sessions (start_time, end_time, duration_seconds, date)
            VALUES (?, ?, ?, ?)
            ''', (start_time.isoformat(), end_time.isoformat(), duration, date_str))
            self.conn.commit()

    def get_sessions_by_date(self, date_str):
        with self.lock:
            cursor = self.conn.cursor()
            cursor.execute('''
            SELECT start_time, end_time, duration_seconds 
            FROM idle_sessions 
            WHERE date = ?
            ''', (date_str,))
            return cursor.fetchall()

# --- 2. Windows API 辅助类 ---
class WindowsAPIHelper:
    @staticmethod
    def get_last_input_info():
        """获取系统自启动或上次用户输入以来经过的毫秒数。"""
        try:
            class LASTINPUTINFO(ctypes.Structure):
                _fields_ = [("cbSize", wintypes.UINT), ("dwTime", wintypes.DWORD)]
            
            lii = LASTINPUTINFO()
            lii.cbSize = ctypes.sizeof(LASTINPUTINFO)
            if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lii)):
                return lii.dwTime
            else:
                return 0
        except Exception as e:
            print(f"获取最后输入信息失败: {e}")
            return 0

    @staticmethod
    def is_workstation_locked():
        """检查工作站是否被锁定"""
        try:
            user32 = ctypes.windll.user32
            h_desktop = user32.OpenInputDesktop(0, False, 0x0100)  # DESKTOP_READOBJECTS
            if h_desktop == 0:
                return True  # 无法打开输入桌面,视为锁定
            user32.CloseDesktop(h_desktop)
            return False
        except Exception:
            return False

# --- 3. 空闲监控模块 ---
class IdleMonitor:
    def __init__(self, threshold_seconds, db_manager, log_callback=None):
        self.threshold = threshold_seconds
        self.db = db_manager
        self.log = log_callback if log_callback else print
        self.thread = None
        self._stop_event = threading.Event()
        self.is_timing = False
        self.session_start_time = None
        self.manual_mode = False

    def _monitor_loop(self):
        last_logged_idle_seconds = -1
        while not self._stop_event.is_set():
            try:
                # 如果处于手动模式,跳过自动检测逻辑
                if self.manual_mode:
                    self._stop_event.wait(1.0)
                    continue

                # 1. 获取系统信息
                last_input_ms = WindowsAPIHelper.get_last_input_info()
                current_ms = ctypes.windll.kernel32.GetTickCount()
                locked = WindowsAPIHelper.is_workstation_locked()

                # 2. 计算空闲时间
                idle_seconds = (current_ms - last_input_ms) / 1000.0

                # 3. 核心状态机逻辑
                if locked:
                    if not self.is_timing:
                        self._start_session()
                else:
                    if idle_seconds > self.threshold:
                        if not self.is_timing:
                            self._start_session()
                    else:
                        if self.is_timing:
                            self._end_session("检测到用户活动,结束空闲会话。")

                # 4. 日志记录
                if int(idle_seconds) != last_logged_idle_seconds:
                    self.log(f"系统空闲时间: {idle_seconds:.1f} 秒")
                    last_logged_idle_seconds = int(idle_seconds)

            except Exception as e:
                self.log(f"[错误] 监控循环发生异常: {e}")
            
            self._stop_event.wait(1.0)

    def _start_session(self):
        self.is_timing = True
        self.session_start_time = datetime.datetime.now()
        self.log(f"⏱️ 开始计时: {self.session_start_time.strftime('%H:%M:%S')}")

    def _end_session(self, reason=""):
        if not self.is_timing:
            return
        self.is_timing = False  # 先重置状态,防止重复触发
        end_time = datetime.datetime.now()
        duration = (end_time - self.session_start_time).total_seconds()
        
        self.log(f"⏱️ 结束计时: {end_time.strftime('%H:%M:%S')} | 原因: {reason} | 持续时间: {duration:.1f} 秒")
        
        try:
            self.db.insert_record(self.session_start_time, end_time, duration)
        except Exception as e:
            self.log(f"[错误] 写入数据库失败: {e}")
        
        self.session_start_time = None

    def start_monitoring(self, manual=False):
        self.manual_mode = manual
        self._stop_event.clear()
        self.log(f"监控已启动,空闲阈值: {self.threshold} 秒。模式: {'手动' if manual else '自动'}")
        self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.thread.start()

    def stop_monitoring(self):
        self._stop_event.set()
        if self.thread:
            self.thread.join(timeout=2.0)
        self.log("监控已停止。")

    def manual_start(self):
        if not self.is_timing:
            self._start_session()

    def manual_stop(self):
        if self.is_timing:
            self._end_session("手动结束计时")

# --- 4. GUI 主应用 ---
class App:
    def __init__(self, root):
        self.root = root
        self.root.title("空闲计时器")
        self.root.geometry("700x500")
        
        self.db_manager = DatabaseManager()
        self.idle_monitor = None

        # --- 配置区 ---
        config_frame = ttk.LabelFrame(root, text="设置")
        config_frame.pack(fill="x", padx=10, pady=5)

        ttk.Label(config_frame, text="空闲阈值 (秒):").grid(row=0, column=0, padx=5, pady=5)
        self.threshold_entry = ttk.Entry(config_frame, width=10)
        self.threshold_entry.grid(row=0, column=1, padx=5, pady=5)
        self.threshold_entry.insert(0, "30")

        # 模式选择
        self.mode_var = tk.StringVar(value="auto")
        ttk.Radiobutton(config_frame, text="自动模式", variable=self.mode_var, value="auto").grid(row=0, column=2, padx=5)
        ttk.Radiobutton(config_frame, text="手动模式", variable=self.mode_var, value="manual").grid(row=0, column=3, padx=5)

        self.start_stop_button = ttk.Button(config_frame, text="启动监控", command=self.toggle_monitoring)
        self.start_stop_button.grid(row=0, column=4, padx=10, pady=5)

        # 手动控制按钮
        self.manual_frame = ttk.Frame(root)
        self.manual_frame.pack(fill="x", padx=10, pady=5)
        
        self.manual_start_btn = ttk.Button(self.manual_frame, text="手动开始计时", 
                                           command=self.manual_start_timer, state='disabled')
        self.manual_start_btn.pack(side='left', padx=5)
        
        self.manual_stop_btn = ttk.Button(self.manual_frame, text="手动结束计时", 
                                         command=self.manual_stop_timer, state='disabled')
        self.manual_stop_btn.pack(side='left', padx=5)

        # --- 统计信息区 ---
        stats_frame = ttk.LabelFrame(root, text="今日统计")
        stats_frame.pack(fill="x", padx=10, pady=5)

        ttk.Label(stats_frame, text="总空闲时间:").grid(row=0, column=0, padx=5, pady=5)
        self.total_duration_var = tk.StringVar(value="0 秒")
        ttk.Label(stats_frame, textvariable=self.total_duration_var).grid(row=0, column=1, padx=5, pady=5)

        ttk.Button(stats_frame, text="刷新统计", command=self.refresh_daily_stats).grid(row=0, column=2, padx=10, pady=5)
        ttk.Button(stats_frame, text="查看历史记录", command=self.show_history).grid(row=0, column=3, padx=10, pady=5)

        # --- 日志区 ---
        log_frame = ttk.LabelFrame(root, text="日志")
        log_frame.pack(fill="both", expand=True, padx=10, pady=5)

        self.log_text = tk.Text(log_frame, state='disabled', height=12)
        self.log_text.pack(side='left', fill="both", expand=True, padx=5, pady=5)
        
        scrollbar = ttk.Scrollbar(log_frame, orient='vertical', command=self.log_text.yview)
        scrollbar.pack(side='right', fill='y')
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log("应用已启动。请设置阈值并点击'启动监控'。")
        self.update_stats_periodically()

    def log(self, message):
        def update_log():
            self.log_text.config(state='normal')
            self.log_text.insert('end', f"{message}\n")
            self.log_text.see('end')
            self.log_text.config(state='disabled')
        self.root.after(0, update_log)

    def toggle_monitoring(self):
        if self.idle_monitor is None or not self.idle_monitor.thread.is_alive():
            threshold = int(self.threshold_entry.get())
            is_manual = self.mode_var.get() == "manual"
            self.idle_monitor = IdleMonitor(threshold, self.db_manager, self.log)
            self.idle_monitor.start_monitoring(manual=is_manual)
            self.start_stop_button.config(text="停止监控")
            
            if is_manual:
                self.manual_start_btn.config(state='normal')
                self.manual_stop_btn.config(state='normal')
            else:
                self.manual_start_btn.config(state='disabled')
                self.manual_stop_btn.config(state='disabled')
        else:
            self.idle_monitor.stop_monitoring()
            self.idle_monitor = None
            self.start_stop_button.config(text="启动监控")
            self.manual_start_btn.config(state='disabled')
            self.manual_stop_btn.config(state='disabled')
        self.refresh_daily_stats()

    def manual_start_timer(self):
        if self.idle_monitor and self.idle_monitor.manual_mode:
            self.idle_monitor.manual_start()

    def manual_stop_timer(self):
        if self.idle_monitor and self.idle_monitor.manual_mode:
            self.idle_monitor.manual_stop()
            self.refresh_daily_stats()

    def refresh_daily_stats(self):
        today_str = datetime.datetime.now().strftime('%Y-%m-%d')
        records = self.db_manager.get_sessions_by_date(today_str)
        total_duration = sum(record[2] for record in records)
        
        hours = int(total_duration // 3600)
        minutes = int((total_duration % 3600) // 60)
        seconds = int(total_duration % 60)
        
        if hours > 0:
            time_str = f"{hours}小时 {minutes}分钟 {seconds}秒"
        elif minutes > 0:
            time_str = f"{minutes}分钟 {seconds}秒"
        else:
            time_str = f"{seconds}秒"
            
        self.total_duration_var.set(time_str)

    def update_stats_periodically(self):
        self.refresh_daily_stats()
        self.root.after(10000, self.update_stats_periodically)

    def show_history(self):
        history_win = tk.Toplevel(self.root)
        history_win.title("历史记录")
        history_win.geometry("800x500")

        top_frame = ttk.Frame(history_win)
        top_frame.pack(fill='x', padx=10, pady=5)
        
        ttk.Label(top_frame, text="选择日期:").grid(row=0, column=0, padx=5, pady=5)
        date_var = tk.StringVar(value=datetime.datetime.now().strftime('%Y-%m-%d'))
        date_entry = ttk.Entry(top_frame, textvariable=date_var, width=15)
        date_entry.grid(row=0, column=1, padx=5, pady=5)

        ttk.Button(top_frame, text="加载记录", command=lambda: load_records()).grid(row=0, column=2, padx=10, pady=5)
        ttk.Button(top_frame, text="关闭", command=history_win.destroy).grid(row=0, column=3, padx=10, pady=5)

        table_frame = ttk.Frame(history_win)
        table_frame.pack(fill='both', expand=True, padx=10, pady=10)

        tree_scroll = ttk.Scrollbar(table_frame)
        tree_scroll.pack(side='right', fill='y')
        
        tree = ttk.Treeview(table_frame, columns=('start', 'end', 'duration'), 
                           show='headings', yscrollcommand=tree_scroll.set)
        tree.heading('start', text='开始时间')
        tree.heading('end', text='结束时间')
        tree.heading('duration', text='持续时间')
        
        tree.column('start', width=200)
        tree.column('end', width=200)
        tree.column('duration', width=150)
        
        tree.pack(side='left', fill='both', expand=True)
        tree_scroll.config(command=tree.yview)

        bottom_frame = ttk.Frame(history_win)
        bottom_frame.pack(fill='x', padx=10, pady=5)
        
        total_var = tk.StringVar(value="总计: 0 秒")
        ttk.Label(bottom_frame, textvariable=total_var).pack(side='left', padx=10)

        def load_records():
            date = date_var.get()
            records = self.db_manager.get_sessions_by_date(date)
            
            for i in tree.get_children():
                tree.delete(i)
                
            total_duration = 0
            for record in records:
                start, end, duration = record
                total_duration += duration
                
                hours = int(duration // 3600)
                minutes = int((duration % 3600) // 60)
                seconds = int(duration % 60)
                duration_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
                
                tree.insert('', 'end', values=(start, end, duration_str))
            
            hours = int(total_duration // 3600)
            minutes = int((total_duration % 3600) // 60)
            seconds = int(total_duration % 60)
            
            if hours > 0:
                total_str = f"总计: {hours}小时 {minutes}分钟 {seconds}秒"
            elif minutes > 0:
                total_str = f"总计: {minutes}分钟 {seconds}秒"
            else:
                total_str = f"总计: {seconds}秒"
                
            total_var.set(total_str)
            
        load_records()

# --- 5. 主程序入口 (关键启动代码) ---
if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()

image.png (44.85 KB, 下载次数: 0)

工具图

工具图

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
 楼主| Yc12138 发表于 2026-8-6 15:10 |楼主
我朋友评价我好无聊,,,emmmm 这就无聊了吗?
3#
iawyxkdn8 发表于 2026-8-6 17:41
4#
ttttyyyy 发表于 2026-8-6 18:46
5#
liyicha 发表于 2026-8-6 19:54
这不会更无聊了吗
6#
willgu520 发表于 2026-8-6 20:54
大神就是多,厉害
7#
Jerry319 发表于 2026-8-6 21:37
软件使用时间(误),键鼠使用时间(),楼主给一个数据导出功能吧
8#
shirker 发表于 2026-8-6 22:26
这是天天很无聊的人了,真羡慕你们
9#
li000yu 发表于 2026-8-6 22:35
如果可以支持周或月统计,然后导出图标显示就更好了
10#
zdyzzgdnnyx 发表于 2026-8-6 23:02
很好奇楼主干嘛的,怎么会有摸鱼时间,而且似乎很多
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-7 04:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表