943 lines
37 KiB
Python
943 lines
37 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import time
|
|
import logging
|
|
import signal
|
|
import sys
|
|
import re
|
|
import argparse
|
|
from pathlib import Path
|
|
from datetime import datetime, timedelta
|
|
|
|
# 配置日志
|
|
def setup_logging():
|
|
apppath="/opt/ktouch"
|
|
log_file = os.path.join(apppath, 'sub_modules.log')
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s display_monitor [%(levelname)s] %(message)s',
|
|
handlers=[
|
|
logging.FileHandler(log_file, encoding='utf-8'),
|
|
logging.StreamHandler(sys.stdout)
|
|
]
|
|
)
|
|
return logging.getLogger(__name__)
|
|
|
|
logger = setup_logging()
|
|
|
|
class DisplayChecker:
|
|
def __init__(self, display_env=":0"):
|
|
"""初始化显示器检查器"""
|
|
self.display_env = display_env
|
|
self.display_name = None
|
|
self.config_data = None
|
|
self.current_settings = None
|
|
|
|
def parse_config_file(self, config_path):
|
|
"""
|
|
解析配置文件
|
|
格式: 分辨率@刷新率|坐标|旋转方向|是否是主屏|模式标志
|
|
示例: 1920x1080@60.0Hz|0,0|1|P|A 或 1920x1080@60.0Hz|0,0|1|P|F
|
|
F: 必须通过cvt生成分辨率并设置
|
|
A: 沿用原来的方法(即xrandr存在分辨率就使用系统分辨率,否则使用cvt)
|
|
"""
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
content = f.read().strip()
|
|
|
|
# 从文件名获取显示器名称(去掉后缀)
|
|
self.display_name = Path(config_path).stem
|
|
|
|
# 解析配置内容
|
|
parts = content.split('|')
|
|
if len(parts) != 5:
|
|
raise ValueError("配置文件格式错误,应该有5个部分")
|
|
|
|
# 解析分辨率和刷新率
|
|
res_refresh_match = re.match(r'(\d+)x(\d+)@([\d.]+)Hz', parts[0])
|
|
if not res_refresh_match:
|
|
raise ValueError("分辨率刷新率格式错误")
|
|
|
|
width, height, refresh_rate = res_refresh_match.groups()
|
|
|
|
# 解析坐标
|
|
pos_match = re.match(r'(-?\d+),(-?\d+)', parts[1])
|
|
if not pos_match:
|
|
raise ValueError("坐标格式错误")
|
|
|
|
pos_x, pos_y = pos_match.groups()
|
|
|
|
# 解析旋转方向
|
|
rotation_map = {'1': 'normal', '2': 'left', '4': 'inverted', '8': 'right'}
|
|
rotation = rotation_map.get(parts[2])
|
|
if not rotation:
|
|
raise ValueError(f"旋转方向错误: {parts[2]}")
|
|
|
|
# 解析主屏设置
|
|
primary = parts[3] == 'P'
|
|
|
|
# 解析模式标志
|
|
mode_flag = parts[4].strip()
|
|
if mode_flag not in ['F', 'A']:
|
|
raise ValueError(f"模式标志错误: {mode_flag},应该是 F 或 A")
|
|
|
|
self.config_data = {
|
|
'width': int(width),
|
|
'height': int(height),
|
|
'refresh_rate': float(refresh_rate),
|
|
'pos_x': int(pos_x),
|
|
'pos_y': int(pos_y),
|
|
'rotation': rotation,
|
|
'primary': primary,
|
|
'mode_flag': mode_flag # 新增字段
|
|
}
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"解析配置文件错误: {e}")
|
|
return False
|
|
|
|
def is_display_connected(self):
|
|
"""检查显示器是否连接"""
|
|
env = os.environ.copy()
|
|
env['DISPLAY'] = self.display_env
|
|
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['xrandr'],
|
|
env=env,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error("获取显示器信息失败")
|
|
return False
|
|
|
|
# 查找显示器连接状态
|
|
lines = result.stdout.split('\n')
|
|
for line in lines:
|
|
if line.startswith(f'{self.display_name} '):
|
|
if 'connected' in line and 'disconnected' not in line:
|
|
return True
|
|
else:
|
|
logger.warning(f"显示器 {self.display_name} 未连接或已断开")
|
|
return False
|
|
|
|
logger.warning(f"未找到显示器 {self.display_name}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"检查显示器连接状态错误: {e}")
|
|
return False
|
|
|
|
def run_xrandr_command(self, cmd, ignore_errors=False):
|
|
"""运行xrandr命令"""
|
|
env = os.environ.copy()
|
|
env['DISPLAY'] = self.display_env
|
|
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
if ignore_errors:
|
|
# 检查是否是"已经存在"之类的错误
|
|
error_lower = result.stderr.lower()
|
|
if any(keyword in error_lower for keyword in ['already exists', 'already set', 'exist']):
|
|
logger.info(f"忽略预期中的错误: {result.stderr.strip()}")
|
|
return True
|
|
else:
|
|
logger.warning(f"命令执行失败但忽略错误: {cmd}")
|
|
logger.warning(f"错误信息: {result.stderr}")
|
|
return True
|
|
else:
|
|
logger.error(f"命令执行失败: {cmd}")
|
|
logger.error(f"错误信息: {result.stderr}")
|
|
return False
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"执行命令错误: {e}")
|
|
return False
|
|
|
|
def get_current_display_settings(self):
|
|
"""获取当前显示器的设置"""
|
|
# 首先检查显示器是否连接
|
|
if not self.is_display_connected():
|
|
return None
|
|
|
|
env = os.environ.copy()
|
|
env['DISPLAY'] = self.display_env
|
|
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['xrandr'],
|
|
env=env,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error("获取显示器信息失败")
|
|
return None
|
|
|
|
# 解析xrandr输出,找到指定显示器的当前设置
|
|
lines = result.stdout.split('\n')
|
|
current_settings = {}
|
|
|
|
for line in lines:
|
|
# 查找目标显示器的连接状态行
|
|
if line.startswith(f'{self.display_name} '):
|
|
# 检查是否是主屏
|
|
current_settings['primary'] = 'primary' in line
|
|
|
|
# 提取当前分辨率
|
|
res_match = re.search(r'(\d+x\d+)\+(-?\d+)\+(-?\d+)', line)
|
|
if res_match:
|
|
current_settings['resolution'] = res_match.group(1)
|
|
current_settings['pos_x'] = int(res_match.group(2))
|
|
current_settings['pos_y'] = int(res_match.group(3))
|
|
|
|
# 提取旋转信息
|
|
if 'inverted' in line and 'x axis y axis' not in line:
|
|
current_settings['rotation'] = 'inverted'
|
|
elif 'left' in line and 'x axis y axis' not in line:
|
|
current_settings['rotation'] = 'left'
|
|
elif 'right' in line and 'x axis y axis' not in line:
|
|
current_settings['rotation'] = 'right'
|
|
else:
|
|
current_settings['rotation'] = 'normal'
|
|
|
|
# 继续查找当前模式行以获取刷新率
|
|
for next_line in lines[lines.index(line)+1:]:
|
|
if next_line.strip() and not next_line.startswith(' '):
|
|
break
|
|
|
|
# 查找当前模式(带*的)
|
|
if '*'+'+' in next_line or '* ' in next_line:
|
|
rate_match = re.search(r'(\d+\.\d+)\*', next_line)
|
|
if rate_match:
|
|
current_settings['refresh_rate'] = float(rate_match.group(1))
|
|
break
|
|
|
|
self.current_settings = current_settings
|
|
return current_settings
|
|
|
|
except Exception as e:
|
|
logger.error(f"获取当前显示器设置错误: {e}")
|
|
return None
|
|
|
|
def get_display_modes(self):
|
|
"""
|
|
获取显示器支持的模式列表
|
|
返回: 字典,键为分辨率,值为该分辨率下支持的刷新率列表
|
|
"""
|
|
# 首先检查显示器是否连接
|
|
if not self.is_display_connected():
|
|
return {}
|
|
|
|
env = os.environ.copy()
|
|
env['DISPLAY'] = self.display_env
|
|
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['xrandr'],
|
|
env=env,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error("获取显示器信息失败")
|
|
return {}
|
|
|
|
# 解析xrandr输出,找到指定显示器的模式
|
|
lines = result.stdout.split('\n')
|
|
modes = {}
|
|
in_target_display = False
|
|
current_resolution = None
|
|
|
|
for line in lines:
|
|
# 检查是否进入目标显示器的部分
|
|
if line.startswith(f'{self.display_name} '):
|
|
in_target_display = True
|
|
continue
|
|
elif in_target_display and line.strip() and not line.startswith(' '):
|
|
# 新的显示器部分开始,退出
|
|
break
|
|
|
|
if in_target_display:
|
|
# 解析分辨率行
|
|
res_match = re.search(r'^\s*(\d+x\d+i?)\s+', line)
|
|
if res_match:
|
|
current_resolution = res_match.group(1)
|
|
modes[current_resolution] = []
|
|
|
|
# 解析刷新率
|
|
if current_resolution:
|
|
rate_matches = re.findall(r'(\d+\.\d+)\*?\+?', line)
|
|
for rate in rate_matches:
|
|
modes[current_resolution].append(float(rate))
|
|
|
|
return modes
|
|
|
|
except Exception as e:
|
|
logger.error(f"获取显示器模式错误: {e}")
|
|
return {}
|
|
|
|
def get_existing_modelines(self):
|
|
"""获取已经存在的自定义模式"""
|
|
env = os.environ.copy()
|
|
env['DISPLAY'] = self.display_env
|
|
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['xrandr'],
|
|
env=env,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
return []
|
|
|
|
# 查找自定义模式
|
|
lines = result.stdout.split('\n')
|
|
modelines = []
|
|
capturing_modelines = True
|
|
|
|
for line in lines:
|
|
if re.match(r'^\S+ connected', line):
|
|
capturing_modelines = False
|
|
continue
|
|
|
|
if capturing_modelines and line.strip():
|
|
mode_match = re.match(r'^\s*(\S+)\s+.*?(\d+\.\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)', line)
|
|
if mode_match:
|
|
modelines.append(mode_match.group(1))
|
|
|
|
return modelines
|
|
|
|
except Exception as e:
|
|
logger.error(f"获取已存在模式错误: {e}")
|
|
return []
|
|
|
|
def find_matching_mode(self, modes):
|
|
"""在支持的模式中查找匹配的模式(允许刷新率误差)"""
|
|
target_res = f"{self.config_data['width']}x{self.config_data['height']}"
|
|
target_refresh = self.config_data['refresh_rate']
|
|
|
|
# 检查精确匹配的分辨率
|
|
if target_res in modes:
|
|
for refresh_rate in modes[target_res]:
|
|
# 允许±2fps的误差
|
|
if abs(refresh_rate - target_refresh) <= 2.0:
|
|
return f"{target_res} {refresh_rate}"
|
|
|
|
# 检查隔行扫描变体
|
|
interlaced_res = f"{target_res}i"
|
|
if interlaced_res in modes:
|
|
for refresh_rate in modes[interlaced_res]:
|
|
if abs(refresh_rate - target_refresh) <= 2.0:
|
|
return f"{interlaced_res} {refresh_rate}"
|
|
|
|
return None
|
|
|
|
def create_and_add_mode(self):
|
|
"""使用cvt创建并添加新的显示模式"""
|
|
width = self.config_data['width']
|
|
height = self.config_data['height']
|
|
refresh_rate = self.config_data['refresh_rate']
|
|
|
|
# 生成模式名称
|
|
mode_name = f"{width}x{height}_{refresh_rate}"
|
|
|
|
# 检查模式是否已经存在
|
|
existing_modelines = self.get_existing_modelines()
|
|
if mode_name in existing_modelines:
|
|
logger.info(f"模式 {mode_name} 已经存在,跳过创建")
|
|
else:
|
|
# 使用cvt生成模式行
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['cvt', str(width), str(height), str(refresh_rate)],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error("cvt命令执行失败")
|
|
return None
|
|
|
|
# 从cvt输出中提取模式行
|
|
lines = result.stdout.split('\n')
|
|
modeline = None
|
|
for line in lines:
|
|
if line.startswith('Modeline '):
|
|
modeline = line.replace('Modeline ', '').strip()
|
|
break
|
|
|
|
if not modeline:
|
|
logger.error("无法从cvt输出中提取模式行")
|
|
return None
|
|
|
|
# 添加新模式
|
|
add_mode_cmd = f"xrandr --newmode {mode_name} {modeline.split(' ', 1)[1]}"
|
|
if not self.run_xrandr_command(add_mode_cmd, ignore_errors=True):
|
|
return None
|
|
logger.info(f"成功创建新模式: {mode_name}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"创建模式错误: {e}")
|
|
return None
|
|
|
|
# 将新模式添加到显示器
|
|
add_to_output_cmd = f"xrandr --addmode {self.display_name} {mode_name}"
|
|
if not self.run_xrandr_command(add_to_output_cmd, ignore_errors=True):
|
|
return None
|
|
|
|
return mode_name
|
|
|
|
def parse_resolution(self, resolution_str):
|
|
"""解析分辨率字符串,返回宽高元组"""
|
|
try:
|
|
if 'x' in resolution_str:
|
|
width, height = resolution_str.split('x')
|
|
height = height.replace('i', '')
|
|
return int(width), int(height)
|
|
return 0, 0
|
|
except:
|
|
return 0, 0
|
|
|
|
def compare_settings(self):
|
|
"""比较当前设置与配置设置"""
|
|
if not self.current_settings or not self.config_data:
|
|
return False, "无法获取当前设置或配置数据"
|
|
|
|
# 检查分辨率(允许±10像素误差)
|
|
current_res = self.current_settings.get('resolution', '')
|
|
current_width, current_height = self.parse_resolution(current_res)
|
|
target_width = self.config_data['width']
|
|
target_height = self.config_data['height']
|
|
|
|
width_diff = abs(current_width - target_width)
|
|
height_diff = abs(current_height - target_height)
|
|
|
|
if width_diff > 10 or height_diff > 10:
|
|
return False, f"分辨率不匹配: 当前 {current_res}, 配置 {target_width}x{target_height}"
|
|
|
|
# 检查刷新率(允许±2fps误差)
|
|
current_refresh = self.current_settings.get('refresh_rate', 0)
|
|
target_refresh = self.config_data['refresh_rate']
|
|
refresh_diff = abs(current_refresh - target_refresh)
|
|
|
|
if refresh_diff > 2.0:
|
|
return False, f"刷新率不匹配: 当前 {current_refresh:.1f}Hz, 配置 {target_refresh:.1f}Hz"
|
|
|
|
# 检查旋转
|
|
current_rotation = self.current_settings.get('rotation', 'normal')
|
|
if current_rotation != self.config_data['rotation']:
|
|
return False, f"旋转不匹配: 当前 {current_rotation}, 配置 {self.config_data['rotation']}"
|
|
|
|
# 检查主屏设置
|
|
current_primary = self.current_settings.get('primary', False)
|
|
if current_primary != self.config_data['primary']:
|
|
primary_status_current = "是" if current_primary else "否"
|
|
primary_status_target = "是" if self.config_data['primary'] else "否"
|
|
return False, f"主屏设置不匹配: 当前 {primary_status_current}, 配置 {primary_status_target}"
|
|
|
|
# 位置不进行校验
|
|
match_details = [
|
|
f"分辨率: {current_res}",
|
|
f"刷新率: {current_refresh:.1f}Hz",
|
|
f"旋转: {current_rotation}",
|
|
f"主屏: {'是' if current_primary else '否'}",
|
|
"位置: 跳过校验"
|
|
]
|
|
|
|
return True, " | ".join(match_details)
|
|
|
|
def configure_display(self):
|
|
"""配置显示器"""
|
|
if not self.config_data:
|
|
logger.error("没有可用的配置数据")
|
|
return False
|
|
|
|
# 检查显示器是否连接
|
|
if not self.is_display_connected():
|
|
logger.warning(f"显示器 {self.display_name} 未连接,跳过配置")
|
|
return True # 返回True表示跳过而不是失败
|
|
|
|
# 获取当前支持的模式
|
|
modes = self.get_display_modes()
|
|
if not modes:
|
|
logger.error(f"无法获取显示器 {self.display_name} 的模式信息")
|
|
return False
|
|
|
|
logger.info(f"显示器 {self.display_name} 支持的模式:")
|
|
for res, rates in modes.items():
|
|
logger.info(f" {res}: {rates}")
|
|
|
|
# 根据模式标志决定行为
|
|
mode_flag = self.config_data.get('mode_flag', 'A') # 默认为A
|
|
|
|
if mode_flag == 'F':
|
|
# F模式:必须使用cvt生成分辨率
|
|
logger.info("模式标志为F,强制使用cvt生成分辨率")
|
|
mode_name = self.create_and_add_mode()
|
|
if not mode_name:
|
|
logger.error("创建新模式失败")
|
|
return False
|
|
else:
|
|
# A模式:沿用原来的方法
|
|
logger.info("模式标志为A,使用自动模式选择")
|
|
mode_name = self.find_matching_mode(modes)
|
|
|
|
# 如果不支持,创建新模式
|
|
if not mode_name:
|
|
logger.info("显示器不支持该模式,尝试创建新模式...")
|
|
mode_name = self.create_and_add_mode()
|
|
if not mode_name:
|
|
logger.error("创建新模式失败")
|
|
return False
|
|
else:
|
|
logger.info(f"找到匹配模式: {mode_name}")
|
|
|
|
# 构建xrandr命令
|
|
cmd_parts = ["xrandr", f"--output {self.display_name}"]
|
|
|
|
# 添加模式
|
|
if ' ' in mode_name:
|
|
resolution, rate = mode_name.split(' ', 1)
|
|
cmd_parts.append(f"--mode {resolution}")
|
|
cmd_parts.append(f"--rate {rate}")
|
|
else:
|
|
cmd_parts.append(f"--mode {mode_name}")
|
|
|
|
# 添加位置
|
|
cmd_parts.append(f"--pos {self.config_data['pos_x']}x{self.config_data['pos_y']}")
|
|
|
|
# 添加旋转
|
|
cmd_parts.append(f"--rotate {self.config_data['rotation']}")
|
|
|
|
# 如果是主屏
|
|
if self.config_data['primary']:
|
|
cmd_parts.append("--primary")
|
|
|
|
# 启用显示器
|
|
cmd_parts.append("--auto")
|
|
|
|
# 执行最终配置命令
|
|
final_cmd = " ".join(cmd_parts)
|
|
logger.info(f"执行命令: {final_cmd}")
|
|
|
|
if self.run_xrandr_command(final_cmd):
|
|
logger.info("显示器配置成功!")
|
|
return True
|
|
else:
|
|
logger.error("显示器配置失败!")
|
|
return False
|
|
|
|
def check_and_configure(self, config_file_path, is_first=False):
|
|
"""检查并配置显示器 - 主要入口方法"""
|
|
# 解析配置文件
|
|
if not self.parse_config_file(config_file_path):
|
|
logger.error(f"解析配置文件失败: {config_file_path}")
|
|
return False
|
|
|
|
# 检查显示器是否连接
|
|
if not self.is_display_connected():
|
|
logger.warning(f"显示器 {self.display_name} 未连接,跳过配置")
|
|
return True # 返回True表示跳过而不是失败
|
|
|
|
# 如果是首次运行,无视显示器配置
|
|
if is_first:
|
|
# logger.info("启动程序后首次设置,开始配置显示器")
|
|
return self.configure_display()
|
|
|
|
# 获取当前设置
|
|
current_settings = self.get_current_display_settings()
|
|
if not current_settings:
|
|
logger.warning("无法获取当前显示器设置,尝试直接配置...")
|
|
return self.configure_display()
|
|
|
|
logger.info("当前显示器设置:")
|
|
for key, value in current_settings.items():
|
|
logger.info(f" {key}: {value}")
|
|
|
|
# 比较设置
|
|
match, message = self.compare_settings()
|
|
|
|
if match:
|
|
logger.info(f"✓ 设置匹配: {message}")
|
|
return True
|
|
else:
|
|
logger.info(f"✗ 设置不匹配: {message}")
|
|
logger.info("开始配置显示器...")
|
|
return self.configure_display()
|
|
|
|
|
|
class FileMonitor:
|
|
def __init__(self):
|
|
self.os_version_file = Path("/etc/os-version")
|
|
self.screen_file = Path("/tmp/ktouch/screen.txt")
|
|
self.settings_dir = Path("/opt/ktouch/display_config")
|
|
self.display_update_file = Path("/tmp/ktouch/display_update.txt")
|
|
|
|
# 存储文件最后修改时间和内容
|
|
self.last_mod_time = None
|
|
self.last_content = None # 新增:存储文件内容
|
|
self.last_display_update_time = 0
|
|
|
|
# 防止重复触发的机制
|
|
self.last_operation_time = None
|
|
self.cooldown_period = 15 # 冷却时间15秒
|
|
|
|
# 设置信号处理,用于优雅退出
|
|
signal.signal(signal.SIGINT, self.signal_handler)
|
|
signal.signal(signal.SIGTERM, self.signal_handler)
|
|
|
|
self.running = True
|
|
self.display_checker = DisplayChecker()
|
|
|
|
def signal_handler(self, signum, frame):
|
|
"""处理中断信号"""
|
|
logger.info(f"接收到信号 {signum},正在退出...")
|
|
self.running = False
|
|
|
|
def check_os_version(self):
|
|
"""检查 /etc/os-version 文件是否存在"""
|
|
if not self.os_version_file.exists():
|
|
logger.error(f"文件 {self.os_version_file} 不存在,退出脚本")
|
|
sys.exit(1)
|
|
logger.info(f"检测到 {self.os_version_file} 文件,继续执行")
|
|
|
|
def wait_for_screen_file(self):
|
|
"""等待 screen.txt 文件生成"""
|
|
while not self.screen_file.exists() and self.running:
|
|
logger.info(f"等待 {self.screen_file} 文件生成...")
|
|
time.sleep(5)
|
|
|
|
if not self.running:
|
|
return False
|
|
|
|
# 获取初始修改时间和内容
|
|
self.last_mod_time = self.screen_file.stat().st_mtime
|
|
self.last_content = self.read_file_content(self.screen_file)
|
|
logger.info(f"检测到 {self.screen_file} 文件,初始修改时间: {time.ctime(self.last_mod_time)}")
|
|
return True
|
|
|
|
def read_file_content(self, file_path):
|
|
"""读取文件内容"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
return f.read().strip()
|
|
except Exception as e:
|
|
logger.error(f"读取文件内容失败 {file_path}: {e}")
|
|
return None
|
|
|
|
def is_in_cooldown_period(self):
|
|
"""检查是否在冷却期内"""
|
|
if self.last_operation_time is None:
|
|
return False
|
|
|
|
elapsed_time = time.time() - self.last_operation_time
|
|
return elapsed_time < self.cooldown_period
|
|
|
|
def update_file_modification_time(self):
|
|
"""更新文件修改时间(用于防止重复触发)"""
|
|
try:
|
|
current_time = time.time()
|
|
os.utime(self.screen_file, (current_time, current_time))
|
|
self.last_mod_time = current_time
|
|
logger.info(f"已更新文件修改时间以防止重复触发")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"更新文件修改时间失败: {e}")
|
|
return False
|
|
|
|
def check_file_modified(self):
|
|
"""检查文件是否被修改(同时检查修改时间和内容)"""
|
|
if not self.screen_file.exists():
|
|
logger.warning(f"文件 {self.screen_file} 不存在")
|
|
return False
|
|
|
|
# 检查是否在冷却期内
|
|
if self.is_in_cooldown_period():
|
|
logger.info("在冷却期内,跳过文件修改检查")
|
|
return False
|
|
|
|
try:
|
|
current_mod_time = self.screen_file.stat().st_mtime
|
|
current_content = self.read_file_content(self.screen_file)
|
|
|
|
# 如果无法读取内容,则跳过
|
|
if current_content is None:
|
|
return False
|
|
|
|
# 检查修改时间是否变化
|
|
if current_mod_time != self.last_mod_time:
|
|
logger.info(f"检测到文件修改时间变化 - 原时间: {time.ctime(self.last_mod_time)}, 新时间: {time.ctime(current_mod_time)}")
|
|
|
|
# 检查内容是否变化
|
|
if current_content != self.last_content:
|
|
logger.info("检测到文件内容实际发生变化,需要执行分辨率设置")
|
|
self.last_mod_time = current_mod_time
|
|
self.last_content = current_content
|
|
return True
|
|
else:
|
|
logger.info("文件修改时间变化但内容未变,只更新修改时间")
|
|
self.last_mod_time = current_mod_time
|
|
# 更新文件修改时间防止重复触发
|
|
self.update_file_modification_time()
|
|
return False
|
|
|
|
except OSError as e:
|
|
logger.error(f"无法获取文件状态: {e}")
|
|
|
|
return False
|
|
|
|
def check_display_update_file(self):
|
|
"""检查display_update.txt文件内容是否为1"""
|
|
if not self.display_update_file.exists():
|
|
# 文件不存在,忽略检测
|
|
return False
|
|
|
|
try:
|
|
# 检查文件修改时间,避免重复处理
|
|
current_mtime = self.display_update_file.stat().st_mtime
|
|
if current_mtime <= self.last_display_update_time:
|
|
return False
|
|
|
|
self.last_display_update_time = current_mtime
|
|
|
|
content = self.read_file_content(self.display_update_file)
|
|
if content == "1":
|
|
logger.info(f"检测到 {self.display_update_file} 文件内容为1,立即进行分辨率设置")
|
|
|
|
# 将文件内容改为0
|
|
try:
|
|
with open(self.display_update_file, 'w') as f:
|
|
f.write("0")
|
|
logger.info(f"已将 {self.display_update_file} 文件内容修改为0")
|
|
except Exception as e:
|
|
logger.error(f"修改display_update.txt文件失败: {e}")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"检查display_update.txt文件失败: {e}")
|
|
|
|
return False
|
|
|
|
def execute_display_settings(self, is_first=False):
|
|
"""执行 display_settings 文件夹中的所有文件 - 直接调用方法"""
|
|
# 检查目录是否存在
|
|
if not self.settings_dir.exists():
|
|
logger.error(f"目录 {self.settings_dir} 不存在")
|
|
return False
|
|
|
|
# 获取目录中的所有文件
|
|
setting_files = list(self.settings_dir.glob("*"))
|
|
if not setting_files:
|
|
logger.warning(f"目录 {self.settings_dir} 中没有文件")
|
|
return True
|
|
|
|
success_count = 0
|
|
total_count = len(setting_files)
|
|
skipped_count = 0
|
|
|
|
for file_path in setting_files:
|
|
if file_path.is_file():
|
|
try:
|
|
logger.info(f"处理显示器配置文件: {file_path}")
|
|
|
|
# 直接调用DisplayChecker的方法,而不是通过子进程
|
|
result = self.display_checker.check_and_configure(str(file_path), is_first)
|
|
if result:
|
|
logger.info(f"成功处理: {file_path}")
|
|
success_count += 1
|
|
else:
|
|
# 检查是否是跳过(显示器未连接)
|
|
if not self.display_checker.is_display_connected():
|
|
logger.warning(f"跳过未连接的显示器: {file_path}")
|
|
skipped_count += 1
|
|
else:
|
|
logger.error(f"处理失败: {file_path}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"处理异常: {file_path}, 异常: {e}")
|
|
|
|
logger.info(f"执行完成: 成功 {success_count}, 跳过 {skipped_count}, 总计 {total_count} 个文件")
|
|
|
|
# 记录操作时间并更新文件修改时间
|
|
self.last_operation_time = time.time()
|
|
|
|
# 等待15秒后更新文件修改时间,防止重复触发
|
|
logger.info(f"等待 {self.cooldown_period} 秒后更新文件修改时间...")
|
|
time.sleep(self.cooldown_period)
|
|
|
|
if self.running:
|
|
self.update_file_modification_time()
|
|
|
|
return success_count > 0 # 只要有一个成功就返回True
|
|
|
|
def initial_resolution_check(self):
|
|
"""初始分辨率检查 - 检查十次,每次间隔15秒"""
|
|
logger.info("开始初始分辨率检查流程...")
|
|
|
|
# 检查目录是否存在
|
|
if not self.settings_dir.exists():
|
|
logger.error(f"目录 {self.settings_dir} 不存在")
|
|
return False
|
|
|
|
# 获取目录中的所有文件
|
|
setting_files = list(self.settings_dir.glob("*"))
|
|
if not setting_files:
|
|
logger.warning(f"目录 {self.settings_dir} 中没有文件")
|
|
return True
|
|
|
|
# 进行十次检查,每次间隔15秒
|
|
max_attempts = 10
|
|
check_interval = 15 # 秒
|
|
|
|
for attempt in range(1, max_attempts + 1):
|
|
if not self.running:
|
|
logger.info("程序正在退出,终止初始分辨率检查")
|
|
return False
|
|
|
|
logger.info(f"初始分辨率检查 - 第 {attempt}/{max_attempts} 次")
|
|
|
|
success_count = 0
|
|
total_count = len(setting_files)
|
|
need_adjustment = False
|
|
|
|
for file_path in setting_files:
|
|
if not self.running:
|
|
break
|
|
|
|
if file_path.is_file():
|
|
try:
|
|
logger.info(f"检查显示器配置文件: {file_path}")
|
|
|
|
# 为每个文件创建新的DisplayChecker实例
|
|
display_checker = DisplayChecker()
|
|
|
|
# 解析配置文件
|
|
if not display_checker.parse_config_file(str(file_path)):
|
|
logger.error(f"解析配置文件失败: {file_path}")
|
|
continue
|
|
|
|
# 检查显示器是否连接
|
|
if not display_checker.is_display_connected():
|
|
logger.warning(f"显示器 {display_checker.display_name} 未连接,跳过检查")
|
|
continue
|
|
|
|
# 获取当前设置
|
|
current_settings = display_checker.get_current_display_settings()
|
|
if not current_settings:
|
|
logger.warning(f"无法获取显示器 {display_checker.display_name} 的当前设置")
|
|
need_adjustment = True
|
|
continue
|
|
|
|
# 比较设置
|
|
match, message = display_checker.compare_settings()
|
|
|
|
if match:
|
|
logger.info(f"✓ 显示器 {display_checker.display_name} 设置匹配: {message}")
|
|
success_count += 1
|
|
else:
|
|
logger.info(f"✗ 显示器 {display_checker.display_name} 设置不匹配: {message}")
|
|
need_adjustment = True
|
|
|
|
# 立即进行配置
|
|
logger.info(f"立即配置显示器 {display_checker.display_name}...")
|
|
if display_checker.configure_display():
|
|
logger.info(f"显示器 {display_checker.display_name} 配置成功")
|
|
success_count += 1
|
|
else:
|
|
logger.error(f"显示器 {display_checker.display_name} 配置失败")
|
|
|
|
except Exception as e:
|
|
logger.error(f"处理异常: {file_path}, 异常: {e}")
|
|
|
|
# 检查是否所有显示器都配置正确
|
|
if success_count == total_count:
|
|
logger.info(f"✓ 第 {attempt} 次检查完成: 所有显示器配置正确")
|
|
break
|
|
else:
|
|
logger.info(f"第 {attempt} 次检查完成: {success_count}/{total_count} 个显示器配置正确")
|
|
|
|
# 如果不是最后一次检查,等待间隔时间
|
|
if attempt < max_attempts:
|
|
if need_adjustment:
|
|
logger.info(f"等待 {check_interval} 秒后进行下一次检查...")
|
|
for i in range(check_interval):
|
|
if not self.running:
|
|
break
|
|
time.sleep(1)
|
|
|
|
logger.info("初始分辨率检查流程完成")
|
|
return True
|
|
|
|
def monitor(self):
|
|
"""主监控循环"""
|
|
logger.info("启动文件监控脚本...")
|
|
|
|
# 检查os-version文件
|
|
self.check_os_version()
|
|
|
|
logger.info("进行首次分辨率设置")
|
|
self.execute_display_settings(is_first=True)
|
|
time.sleep(10)
|
|
|
|
# 等待screen文件生成
|
|
if not self.wait_for_screen_file():
|
|
return
|
|
|
|
# 在screen.txt生成后,进行初始分辨率检查
|
|
logger.info("screen.txt文件已生成,开始初始分辨率检查...")
|
|
self.initial_resolution_check()
|
|
|
|
logger.info("开始监控文件变化...")
|
|
|
|
while self.running:
|
|
try:
|
|
# 检查display_update.txt文件
|
|
if self.check_display_update_file():
|
|
logger.info("检测到display_update.txt触发,立即执行分辨率设置...")
|
|
self.execute_display_settings()
|
|
logger.info("display_update.txt触发操作完成,继续监控...")
|
|
|
|
# 检查screen.txt文件是否被修改
|
|
if self.check_file_modified():
|
|
logger.info("检测到screen.txt文件内容变化,执行display_settings中的文件...")
|
|
self.execute_display_settings()
|
|
logger.info("screen.txt变化操作完成,继续监控...")
|
|
|
|
# 等待1秒后继续检查
|
|
time.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("用户中断监控")
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"监控过程中发生异常: {e}")
|
|
time.sleep(10)
|
|
|
|
logger.info("监控脚本已停止")
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
monitor = FileMonitor()
|
|
monitor.monitor()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |