import os
import time
import win32com.client
import win32gui
import win32con
from pythoncom import CoInitialize, CoUninitialize # 显式导入COM函数
import threading
import psutil
import pyautogui
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
# 移除独立的日志配置,使用 app.py 的日志系统
class PPTController:
"""控制PPT的全屏播放和翻页"""
_is_active = False # 类属性,标记是否有活跃的PPT
_is_ready = False # 类属性,标记PPT是否已经准备好(即已经全屏并开始放映)
_presentation = None # 当前打开的演示文稿对象
_application = None # PowerPoint应用程序对象
_lock = threading.Lock() # 添加线程锁
_logger = None # 日志记录器
_com_initialized = False # 标记COM是否已初始化
@classmethod
def set_logger(cls, logger):
"""设置日志记录器"""
cls._logger = logger
@classmethod
def open_fullscreen(cls, ppt_path):
"""打开PPT并全屏播放(新增循环播放功能)"""
# 如果已经有打开的PPT,先关闭
if cls._is_active:
cls.close()
try:
with cls._lock: # 使用线程锁确保线程安全
# 初始化 COM
try:
CoInitialize()
cls._com_initialized = True
if cls._logger:
cls._logger.debug("COM初始化成功")
except Exception as e:
if cls._logger:
cls._logger.error(f"COM初始化失败: {str(e)}")
return False, f"COM初始化失败: {str(e)}"
# 检查文件是否存在
if not os.path.exists(ppt_path):
if cls._logger:
cls._logger.error(f"PPT文件不存在: {ppt_path}")
# 清理COM资源
cls._safe_uninitialize()
return False, "PPT文件不存在"
# 尝试使用PowerPoint,如果失败则尝试WPS
try:
cls._application = win32com.client.Dispatch("PowerPoint.Application")
if cls._logger:
cls._logger.info("使用Microsoft PowerPoint打开文件")
is_powerpoint = True # 标记是否为PowerPoint
except Exception as e:
if cls._logger:
cls._logger.warning(f"无法启动PowerPoint, 尝试WPS: {str(e)}")
try:
cls._application = win32com.client.Dispatch("KWPP.Application")
if cls._logger:
cls._logger.info("使用WPS演示打开文件")
is_powerpoint = False # 标记为WPS
except Exception as e2:
if cls._logger:
cls._logger.error(f"无法启动WPS: {str(e2)}")
# 清理资源
cls._safe_uninitialize()
return False, f"无法启动PowerPoint或WPS: {str(e)}"
cls._application.Visible = True
# 打开演示文稿
try:
cls._presentation = cls._application.Presentations.Open(ppt_path, WithWindow=True)
except Exception as e:
if cls._logger:
cls._logger.error(f"打开PPT文件失败: {str(e)}")
# 清理资源
cls._cleanup_resources()
return False, f"打开PPT文件失败: {str(e)}"
# 全屏放映(新增循环设置)
try:
slide_show_settings = cls._presentation.SlideShowSettings
# 设置循环播放:PowerPoint和WPS的属性名不同,需分别处理
if is_powerpoint:
# PowerPoint中,LoopUntilStopped=True 表示循环播放直到手动停止
slide_show_settings.LoopUntilStopped = True
else:
# WPS中,Loop=True 表示循环播放
slide_show_settings.Loop = True
# 启动放映
slide_show_settings.Run()
except Exception as e:
if cls._logger:
cls._logger.error(f"启动幻灯片放映失败: {str(e)}")
# 清理资源
cls._cleanup_resources()
return False, f"启动幻灯片放映失败: {str(e)}"
# 最大化并置顶窗口
time.sleep(1) # 等待窗口创建
cls.maximize_window()
cls._is_active = True
cls._is_ready = True
if cls._logger:
cls._logger.info(f"PPT已全屏循环播放: {ppt_path}")
return True, "PPT已全屏循环播放"
except Exception as e:
# 异常时清理资源
cls._cleanup_resources()
if cls._logger:
cls._logger.error(f"打开PPT失败: {str(e)}")
return False, f"打开PPT失败: {str(e)}"
@classmethod
def _cleanup_resources(cls):
"""清理PPT相关资源"""
if cls._presentation:
try:
cls._presentation.Close()
cls._presentation = None
except:
pass
if cls._application:
try:
cls._application.Quit()
cls._application = None
except:
pass
# 反初始化COM
cls._safe_uninitialize()
cls._is_active = False
cls._is_ready = False
@classmethod
def _safe_uninitialize(cls):
"""安全地反初始化COM"""
if cls._com_initialized:
try:
CoUninitialize()
cls._com_initialized = False
if cls._logger:
cls._logger.debug("已反初始化COM")
except Exception as e:
if cls._logger:
cls._logger.warning(f"反初始化COM失败: {str(e)}")
else:
if cls._logger:
cls._logger.debug("COM未初始化,无需反初始化")
@classmethod
def maximize_window(cls):
if not cls._presentation:
return False
try:
def enum_windows_callback(hwnd, _):
window_title = win32gui.GetWindowText(hwnd)
if "PowerPoint Slide Show" in window_title or "WPS 演示" in window_title:
# 先最大化窗口
win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
# 再设置窗口置顶(HWND_TOPMOST表示置顶,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE表示不改变位置和大小)
win32gui.SetWindowPos(
hwnd,
win32con.HWND_TOPMOST, # 置顶标记
0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE # 不改变位置和大小
)
if cls._logger:
cls._logger.info(f"最大化并置顶窗口: {window_title}")
win32gui.EnumWindows(enum_windows_callback, None)
return True
except Exception as e:
if cls._logger:
cls._logger.warning(f"最大化并置顶窗口失败: {str(e)}")
return False
@classmethod
def navigate(cls, direction):
"""翻页操作:通过模拟键盘上下键实现(核心修改)"""
if not cls._is_active or not cls._application or not cls._presentation:
return False, "没有活跃的PPT或对象已失效"
if not cls._is_ready:
return False, "PPT正在加载中,请稍后"
try:
with cls._lock:
# 检查放映窗口是否存在(确保PPT处于放映状态)
if not hasattr(cls._presentation, 'SlideShowWindow') or cls._presentation.SlideShowWindow is None:
return False, "幻灯片放映已终止,请重新打开"
# 模拟键盘按键(上箭头=上一页,下箭头=下一页)
if direction == 'next':
pyautogui.press('down') # 模拟下箭头键
if cls._logger:
cls._logger.info("模拟下箭头键,切换到下一页")
return True, "已模拟下箭头键,切换到下一页"
elif direction == 'previous':
pyautogui.press('up') # 模拟上箭头键
if cls._logger:
cls._logger.info("模拟上箭头键,切换到上一页")
return True, "已模拟上箭头键,切换到上一页"
else:
return False, "无效的翻页方向"
except Exception as e:
if cls._logger:
cls._logger.error(f"翻页失败: {str(e)}")
return False, f"翻页失败: {str(e)}"
@classmethod
def close(cls):
"""关闭当前打开的PPT"""
with cls._lock: # 使用线程锁确保线程安全
cls._cleanup_resources()
if cls._logger:
cls._logger.info("PPT已关闭")
@classmethod
def is_active(cls):
"""检查是否有活跃的PPT"""
return cls._is_active
@classmethod
def is_ready(cls):
"""检查PPT是否准备好(可翻页)"""
return cls._is_ready
class WebController:
"""控制网页的全屏显示"""
_driver = None # WebDriver实例
_lock = threading.Lock() # 添加线程锁
_logger = None # 日志记录器
@classmethod
def set_logger(cls, logger):
"""设置日志记录器"""
cls._logger = logger
@classmethod
def open_fullscreen(cls, url, browser_type='chrome'):
"""
在浏览器中全屏打开网页
:param url: 要打开的URL
:param browser_type: 浏览器类型,支持 'chrome', 'edge', 'firefox'
:return: (成功与否, 消息)
"""
with cls._lock: # 使用线程锁确保线程安全
# 关闭已存在的浏览器实例
if cls._driver:
try:
cls._driver.quit()
except:
pass
cls._driver = None
try:
# 根据浏览器类型创建driver
if browser_type.lower() in ['chrome', 'googlechrome']:
chrome_options = Options()
chrome_options.add_argument("--kiosk") # 全屏模式
chrome_options.add_argument("--disable-infobars")
chrome_options.add_experimental_option("useAutomationExtension", False)
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
# 设置WebDriver服务
service = Service(executable_path='chromedriver.exe')
cls._driver = webdriver.Chrome(service=service, options=chrome_options)
elif browser_type.lower() in ['edge', 'microsoftedge']:
edge_options = Options()
edge_options.add_argument("--kiosk") # 全屏模式
edge_options.add_argument("--disable-infobars")
# 设置WebDriver服务
service = Service(executable_path='msedgedriver.exe')
cls._driver = webdriver.Edge(service=service, options=edge_options)
elif browser_type.lower() in ['firefox', 'mozilla']:
firefox_options = webdriver.FirefoxOptions()
firefox_options.add_argument("--kiosk") # 全屏模式
# 设置WebDriver服务
service = Service(executable_path='geckodriver.exe')
cls._driver = webdriver.Firefox(service=service, options=firefox_options)
else:
if cls._logger:
cls._logger.error(f"不支持的浏览器类型: {browser_type}")
return False, f"不支持的浏览器类型: {browser_type}"
# 打开URL
cls._driver.get(url)
# 确保全屏
try:
# 尝试按F11实现全屏(某些浏览器需要)
ActionChains(cls._driver).key_down(Keys.F11).perform()
time.sleep(0.5) # 等待全屏生效
except:
pass
if cls._logger:
cls._logger.info(f"已在{browser_type}中全屏打开: {url}")
return True, f"已在{browser_type}中全屏打开"
except Exception as e:
if cls._logger:
cls._logger.error(f"打开网页失败: {str(e)}")
return False, f"打开网页失败: {str(e)}"
@classmethod
def close(cls):
"""关闭浏览器"""
with cls._lock: # 使用线程锁确保线程安全
if cls._driver:
try:
cls._driver.quit()
cls._driver = None
if cls._logger:
cls._logger.info("浏览器已关闭")
return True
except Exception as e:
if cls._logger:
cls._logger.error(f"关闭浏览器失败: {str(e)}")
return False
return True
def kill_process_by_name(process_names, logger=None):
"""根据进程名杀死进程"""
for proc in psutil.process_iter(['name']):
try:
if proc.info['name'] in process_names:
proc.kill()
if logger:
logger.info(f"已结束进程: {proc.info['name']}")
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess) as e:
if logger:
logger.warning(f"无法结束进程: {e}")
except Exception as e:
if logger:
logger.error(f"结束进程时出错: {e}")
def cleanup_processes(logger=None):
"""清理可能残留的进程"""
# 清理PPT相关进程
kill_process_by_name([
"POWERPNT.EXE", # PowerPoint
"wpp.exe", # WPS演示
"et.exe", # WPS表格
"wps.exe", # WPS文字
"chrome.exe", # Chrome
"msedge.exe", # Edge
"firefox.exe" # Firefox
], logger=logger) 如果 不使用pycom 有没有办法控制ppt
最新发布