|
- # -*- coding: utf-8 -*-
- import sys
- import threading
- import time
- import queue
- import tkinter as tk
- import tkinter.messagebox as messagebox
- import win32api
- import win32con
- import win32gui
- import win32gui_struct
- # DPI感知(高DPI屏幕支持)
- try:
- import ctypes
- ctypes.windll.shcore.SetProcessDpiAwareness(1) # 1=系统DPI感知, 2=每监视器DPI感知
- except Exception:
- pass
- APP_NAME = "息屏脚本"
- TRAY_TOOLTIP = APP_NAME
- TRAY_ICON = win32con.IDI_APPLICATION # 使用默认应用程序图标
- # 鼠标移动范围
- X_MIN = 100
- X_MAX = 1000
- Y_FIXED = 200 # 固定Y坐标,可以根据实际需要修改
- # ==========================
- # 鼠标移动线程
- # ==========================
- class MouseMover(threading.Thread):
- def __init__(self, coord_queue, lock, stop_event):
- super().__init__(daemon=True)
- self.coord_queue = coord_queue
- self.lock = lock
- self.stop_event = stop_event
- self.direction = 1 # 1:右, -1:左
- self.x = X_MIN
- self.y = Y_FIXED
- def run(self):
- try:
- while not self.stop_event.is_set():
- with self.lock:
- # 计算新X坐标
- self.x += self.direction
- if self.x >= X_MAX:
- self.x = X_MAX
- self.direction = -1
- elif self.x <= X_MIN:
- self.x = X_MIN
- self.direction = 1
- # 移动鼠标
- win32api.SetCursorPos((self.x, self.y))
- # 通知主线程当前坐标
- self.coord_queue.queue.clear() # 保证只保留最新
- self.coord_queue.put_nowait((self.x, self.y))
- time.sleep(60) # 每60秒移动一次
- except Exception as e:
- # 线程内部异常打印,可扩展为日志
- print(f"[MouseMover] {e}")
- # ==========================
- # 系统托盘线程
- # ==========================
- class SysTrayIcon(threading.Thread):
- def __init__(self, on_restore, on_quit):
- super().__init__(daemon=True)
- self.on_restore = on_restore
- self.on_quit = on_quit
- self.hwnd = None
- self.notify_id = None
- self.running = threading.Event()
- def show_icon(self):
- # 创建窗口
- message_map = {
- win32con.WM_DESTROY: self.on_destroy,
- win32con.WM_COMMAND: self.on_command,
- win32con.WM_USER+20: self.on_tray_notify,
- }
- wc = win32gui.WNDCLASS()
- hinst = wc.hInstance = win32api.GetModuleHandle(None)
- wc.lpszClassName = "SysTrayApp"
- wc.lpfnWndProc = message_map
- class_atom = win32gui.RegisterClass(wc)
- self.hwnd = win32gui.CreateWindow(
- class_atom, APP_NAME, 0,
- 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
- 0, 0, hinst, None
- )
- self.notify_id = (self.hwnd, 0, win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
- win32con.WM_USER+20, win32gui.LoadIcon(0, TRAY_ICON), TRAY_TOOLTIP)
- win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self.notify_id)
- self.running.set()
- win32gui.PumpMessages()
- def on_destroy(self, hwnd, msg, wparam, lparam):
- if self.notify_id:
- win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self.notify_id)
- win32gui.PostQuitMessage(0)
- return 0
- def on_tray_notify(self, hwnd, msg, wparam, lparam):
- if lparam == win32con.WM_RBUTTONUP:
- self.show_menu()
- elif lparam == win32con.WM_LBUTTONDBLCLK:
- self.on_restore()
- return 0
- def show_menu(self):
- menu = win32gui.CreatePopupMenu()
- win32gui.AppendMenu(menu, win32con.MF_STRING, 1023, "显示窗口")
- win32gui.AppendMenu(menu, win32con.MF_STRING, 1024, "退出程序")
- pos = win32gui.GetCursorPos()
- win32gui.SetForegroundWindow(self.hwnd)
- win32gui.TrackPopupMenu(menu, win32con.TPM_LEFTALIGN, pos[0], pos[1], 0, self.hwnd, None)
- win32gui.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
- def on_command(self, hwnd, msg, wparam, lparam):
- id = win32api.LOWORD(wparam)
- if id == 1023: # 显示窗口
- self.on_restore()
- elif id == 1024: # 退出
- self.on_quit()
- return 0
- def run(self):
- try:
- self.show_icon()
- except Exception as e:
- print(f"[SysTrayIcon] {e}")
- def close(self):
- # 线程安全关闭托盘
- if self.hwnd:
- try:
- win32gui.PostMessage(self.hwnd, win32con.WM_DESTROY, 0, 0)
- except Exception:
- pass
- # ==========================
- # 主GUI窗口
- # ==========================
- class MainApp:
- def __init__(self, root):
- self.root = root
- self.root.title(APP_NAME)
- self.root.protocol("WM_DELETE_WINDOW", self.on_close)
- self.root.geometry("300x150")
- self.root.resizable(False, False)
- # 共享资源
- self.coord_queue = queue.Queue()
- self.coord_lock = threading.Lock()
- self.mover_stop = threading.Event()
- self.mover_thread = None
- # 托盘
- self.tray_thread = None
- # UI
- self.coord_label = tk.Label(root, text="当前鼠标坐标:", font=("微软雅黑", 12))
- self.coord_label.pack(pady=10)
- self.start_btn = tk.Button(root, text="开始", width=10, command=self.start_move)
- self.start_btn.pack(pady=5)
- self.stop_btn = tk.Button(root, text="结束", width=10, command=self.stop_move, state=tk.DISABLED)
- self.stop_btn.pack()
- # 每秒刷新鼠标坐标
- self.update_coord_label()
- self.root.after(1000, self.refresh_coord_from_sys)
- def start_move(self):
- if self.mover_thread and self.mover_thread.is_alive():
- return
- self.mover_stop.clear()
- self.mover_thread = MouseMover(self.coord_queue, self.coord_lock, self.mover_stop)
- self.mover_thread.start()
- self.start_btn.config(state=tk.DISABLED)
- self.stop_btn.config(state=tk.NORMAL)
- def stop_move(self):
- self.mover_stop.set()
- self.start_btn.config(state=tk.NORMAL)
- self.stop_btn.config(state=tk.DISABLED)
- def update_coord_label(self):
- try:
- pos = win32api.GetCursorPos()
- self.coord_label.config(text=f"当前鼠标坐标:({pos[0]}, {pos[1]})")
- except Exception:
- self.coord_label.config(text="无法获取坐标")
- self.root.after(1000, self.update_coord_label)
- def refresh_coord_from_sys(self):
- # 从队列获取由后台线程移动后的坐标(如果有)
- try:
- while not self.coord_queue.empty():
- x, y = self.coord_queue.get_nowait()
- self.coord_label.config(text=f"当前鼠标坐标:({x}, {y})")
- except Exception:
- pass
- self.root.after(1000, self.refresh_coord_from_sys)
- def on_close(self):
- # 最小化到托盘
- self.hide_to_tray()
- def hide_to_tray(self):
- self.root.withdraw()
- if not self.tray_thread or not self.tray_thread.is_alive():
- self.tray_thread = SysTrayIcon(self.show_from_tray, self.quit_app)
- self.tray_thread.start()
- def show_from_tray(self):
- def _show():
- self.root.deiconify()
- self.root.after(100, self.root.lift)
- self.root.after(0, _show)
- if self.tray_thread:
- self.tray_thread.close()
- self.tray_thread = None
- def quit_app(self):
- # 主动退出、资源清理
- try:
- self.stop_move()
- if self.tray_thread:
- self.tray_thread.close()
- self.tray_thread = None
- self.root.after(200, self.root.destroy)
- except Exception:
- sys.exit(0)
- # ==========================
- # 程序入口
- # ==========================
- def main():
- root = tk.Tk()
- app = MainApp(root)
- try:
- root.mainloop()
- except KeyboardInterrupt:
- pass
- if __name__ == '__main__':
- main()
复制代码 |
|