Python进程守护示例

import base64
import io
import subprocess
import psutil
from datetime import datetime
import os
from pathlib import Path
import win32com.client
import win32process
import cv2
import numpy as np
import win32ui
import ctypes
import win32gui
import win32con
import win32api
import time
from PIL import Image



# 将Base64数据解码为图片
def base64_to_image(base64_data):
    """base64_png"""
    image_data = base64.b64decode(base64_data)
    image = Image.open(io.BytesIO(image_data))
    return image

def find_template(screenshot, template_path, threshold=0.8):
    """在截图中查找模板图片,返回匹配位置"""
    # 读取模板图片
    template = cv2.imread(template_path, 0)
    if template is None:
        print(f"无法读取模板图片: {template_path}")
        return None

    # 转换截图为灰度图
    screenshot_gray = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

    # 模板匹配
    result = cv2.matchTemplate(screenshot_gray, template, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

    # 如果匹配度超过阈值,返回中心点坐标
    if max_val >= threshold:
        h, w = template.shape
        center_x = max_loc[0] + w // 2
        center_y = max_loc[1] + h // 2
        return int(center_x), int(center_y), max_val
    else:
        print(f"未找到匹配项,最高匹配度: {max_val:.2f}")
        return None

def click_in_window(hwnd, x, y):
    """在指定窗口的相对坐标处执行鼠标单击"""
    if not hwnd:
        return False

    # 获取窗口位置和大小
    left, top, right, bottom = win32gui.GetClientRect(hwnd)
    window_width = right - left
    window_height = bottom - top

    # 检查坐标是否在窗口内
    if x < 0 or x >= window_width or y < 0 or y >= window_height:
        print(f"坐标 ({x}, {y}) 超出窗口范围 ({window_width}, {window_height})")
        return False

    # 将客户端坐标转换为屏幕坐标
    screen_x, screen_y = win32gui.ClientToScreen(hwnd, (x, y))

    # 激活窗口
    win32gui.SetForegroundWindow(hwnd)
    time.sleep(0.1)  # 等待窗口激活

    # 执行鼠标单击
    win32api.SetCursorPos((screen_x, screen_y))
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, screen_x, screen_y, 0, 0)
    time.sleep(0.05)  # 模拟真实点击的按下时间
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, screen_x, screen_y, 0, 0)

    return True

def Capture_Win32_Gpu(handle: int, model: int = 3):
    """
    通过win32方式截图,并且无视硬件加速
    https://stackoverflow.com/questions/19695214/screenshot-of-inactive-window-printwindow-win32gui
    """
    ctypes.windll.user32.SetProcessDPIAware()  # 抑制缩放

    rect = win32gui.GetWindowRect(handle)
    width, height = rect[2] - rect[0], rect[3] - rect[1]

    hwnd_dc = win32gui.GetWindowDC(handle)
    mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
    save_dc = mfc_dc.CreateCompatibleDC()
    save_bit_map = win32ui.CreateBitmap()

    save_bit_map.CreateCompatibleBitmap(mfc_dc, width, height)
    save_dc.SelectObject(save_bit_map)

    ctypes.windll.user32.PrintWindow(handle, save_dc.GetSafeHdc(), model)  # 如果仍然是黑屏,尝试修改数字 ‘3’ (从0开始...)
    bmpinfo = save_bit_map.GetInfo()
    bmpstr = save_bit_map.GetBitmapBits(True)

    capture = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo["bmHeight"], bmpinfo["bmWidth"], 4))
    capture = np.ascontiguousarray(capture)[..., :-1]

    win32gui.DeleteObject(save_bit_map.GetHandle())
    save_dc.DeleteDC()
    mfc_dc.DeleteDC()
    win32gui.ReleaseDC(handle, hwnd_dc)

    capture = cv2.cvtColor(capture, cv2.COLOR_RGBA2RGB)
    return capture

def get_desktop_path():
    """获取当前用户的桌面路径"""
    return os.path.join(os.path.expanduser("~"), "Desktop")

def get_shortcut_target(lnk_path):
    """获取快捷方式指向的目标路径"""
    try:
        shell = win32com.client.Dispatch("WScript.Shell")
        shortcut = shell.CreateShortCut(lnk_path)
        return shortcut.TargetPath
    except Exception as e:
        print(f"Error reading shortcut {lnk_path}: {e}")
        return None

def find_shortcuts_by_name(query, directory=None):
    """
    根据名称搜索桌面快捷方式并返回目标路径

    参数:
        query (str): 要搜索的快捷方式名称(不包含.lnk扩展名)
        directory (str, optional): 要搜索的目录路径,默认为桌面

    返回:
        list: 包含匹配的快捷方式及其目标路径的列表
    """
    if directory is None:
        directory = get_desktop_path()

    results = []

    try:
        # 遍历目录下所有.lnk文件
        for lnk_file in Path(directory).rglob("*.lnk"):
            # 获取不包含扩展名的文件名
            file_name = lnk_file.stem

            # 检查文件名是否包含查询字符串(不区分大小写)
            if query.lower() in file_name.lower():
                target = get_shortcut_target(str(lnk_file))
                if target:
                    results.append({
                        'shortcut_path': str(lnk_file),
                        'target_path': target
                    })

    except Exception as e:
        print(f"Error searching for shortcuts: {e}")

    return results

def get_window_handle(title:str):
    """根据窗口标题获取窗口句柄"""
    hwnd = win32gui.FindWindow(None, title)
    return hwnd if hwnd != 0 else None

def is_window_alive(title:str):
    """检查指定标题的窗口是否存在"""
    return get_window_handle(title) is not None

def find_lnk_files(desktop_path:str):
    """根据指定的桌面快捷方式名称寻找其指向的文件"""
    shortcuts = find_shortcuts_by_name(desktop_path)

    if shortcuts:
        for shortcut in shortcuts:
            return f"{shortcut['target_path']}"
    else:
            return None

def launch_application(exe_path:str):
    """启动应用程序"""
    if not exe_path or not os.path.exists(exe_path):
        print(f"错误: 应用程序路径不存在 - {exe_path}")
        return False

    try:
        subprocess.Popen(exe_path)
        print(f"已启动应用程序: {exe_path}")
        return True
    except Exception as e:
        print(f"启动应用程序失败: {e}")
        return False

def get_process_id_from_hwnd(hwnd):
    """从窗口句柄获取进程ID"""
    if not hwnd:
        return None

    try:
        _, process_id = win32process.GetWindowThreadProcessId(hwnd)
        return process_id
    except:
        return None

def get_process_exe(pid):
    """获取进程的可执行文件路径"""
    if not pid:
        return None

    try:
        process = psutil.Process(pid)
        return process.exe()
    except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        return None


def main(w_title:str,d_title:str,lnk_title:str):
    """主函数"""
    print("窗口监控与重启工具已启动")
    print("=" * 50)
    check_interval = 1  # 检查间隔(秒)
    last_seen = datetime.now()
    restart_delay = 1  # 检测到窗口关闭后等待的时间(秒)
    consecutive_misses = 0
    max_consecutive_misses = 1  # 连续检测不到窗口的最大次数

    try:
        while True:
            current_time = datetime.now()
            if is_window_alive(w_title):
                last_seen = current_time
                consecutive_misses = 0
                print(f"\r窗口状态: 正常运行中 - 上次检查: {current_time.strftime('%H:%M:%S')}", end="")
            else:
                consecutive_misses += 1
                time_since_close = (current_time - last_seen).total_seconds()
                print(
                    f"\r窗口状态: 已关闭 {time_since_close:.1f} 秒 - 连续检测失败: {consecutive_misses}/{max_consecutive_misses}",
                    end="")

                if consecutive_misses >= max_consecutive_misses:
                    print(f"\n检测到窗口已关闭超过 {restart_delay} 秒,正在尝试重启...")
                    exe_path=find_lnk_files(lnk_title) #若不存在通过桌面快捷方式运行指定软件
                    launch_application(exe_path) #启动软件
                    time.sleep(3)
                    # click_login(d_title,r'C:\Users\Administrator\Desktop\rjsh\logo.png',r'C:\Users\Administrator\Desktop\rjsh\dl.png') #使用opencv处理登录
                    if is_window_alive(d_title):
                        hwnd = get_window_handle(d_title)
                        print(f"检测到登录窗口: {d_title} (句柄: {hwnd})")
                        screenshot = Capture_Win32_Gpu(hwnd)  # 使用win32api截图指定窗口,模式3
                        while True:
                            if screenshot is not None:
                                match_pos = find_template(screenshot, r'C:\Users\Administrator\Desktop\rjsh\logo.png', 0.8)  # 检测登录页面是否加载完成
                                if match_pos is not None:
                                    # 登录页面加载完成
                                    res = find_template(screenshot, r'C:\Users\Administrator\Desktop\rjsh\dl.png', 0.8)
                                    print(f"找到登录按钮,位置: {res[:2]},匹配度: {res[2]:.2f}")
                                    click_in_window(hwnd, res[0], res[1])  # 使用win32api点击登录
                                    break
                                else:
                                    # 登录页面没加载完成
                                    print(f"等待软件启动中")
                                    time.sleep(2)

                    consecutive_misses = 0
                    last_seen = current_time

            time.sleep(check_interval)

    except KeyboardInterrupt:
        print("\n接收到退出信号")
        print("监控已停止")
    except Exception as e:
        print(f"\n发生错误: {e}")
        print("监控已停止")

if __name__ == '__main__':
    main('','','')