Files
kefu/deploy/dpi-adaptation-20260916/prepare.py
T
2026-09-21 10:34:06 +08:00

147 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
import ast,hashlib,json,re,shutil
root=Path('C:/wechat_rpa');folder=Path(__file__).resolve().parent
payload=folder/'payload';payload.mkdir(exist_ok=True)
names=['wechat_gui_qt.py','assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html','test_qt_console_bridge.py']
manifest=[]
for name in names:
dest=payload/name;dest.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(root/name,dest)
manifest.append({'path':name,'before_sha256':hashlib.sha256((root/name).read_bytes()).hexdigest()})
(folder/'before.json').write_text(json.dumps(manifest,indent=2),encoding='utf-8')
path=payload/'assets/ui/console-app.js';text=path.read_text(encoding='utf-8')
start=text.index('fit = function(){');end=text.index('\nfunction connectQt()',start)
text=text[:start]+'''// CSS pixels are logical pixels: Qt/Chromium applies the monitor DPI once.
// Preserve the familiar 1080p density without shrinking text to fit page height.
const CONSOLE_BASE_SCALE = 0.85;
fit = function(){
document.documentElement.classList.add("embedded");
const stage = document.getElementById("stage");
if (!stage) return;
const preference = String((STATE.system || {}).scale || "自动");
const choices = {"100%":1,"125%":1.25,"150%":1.5,"200%":2,"250%":2.5,"300%":3};
const scale = CONSOLE_BASE_SCALE * (choices[preference] || 1);
const width = innerWidth / scale;
stage.style.zoom = String(scale);
stage.style.transform = "none";
stage.style.width = width + "px";
stage.style.height = innerHeight / scale + "px";
stage.style.setProperty("--scale", String(scale));
stage.dataset.layout = width < 900 ? "narrow" : (width < 1440 ? "compact" : "wide");
stage.dataset.short = String(innerHeight / scale < 800);
};
''' + text[end:]
text=text.replace('["自动","100%","125%","150%","200%"]','["自动","100%","125%","150%","200%","250%","300%"]')
text=text.replace('缩放比例:','界面大小:')
text=text.replace('id="sys-scale"','id="sys-scale" title="自动跟随系统显示缩放;百分比在系统基础上调整界面大小,立即生效"')
text=text.replace(' // 换页之后内容高度变了,缩放要跟着重算——不然新页面要么被裁掉一截,\n // 要么下面留一大片空白。',' // Keep the same density between pages; update only the responsive layout.')
path.write_text(text,encoding='utf-8',newline='\n')
path=payload/'assets/ui/zhenyang-ai-console.html';text=path.read_text(encoding='utf-8')
assert text.count('</head>')==1
text=text.replace('</head>',' <link rel="stylesheet" href="console-responsive.css?v=dpi1">\n</head>')
text=text.replace('console-app.js?v=src3','console-app.js?v=dpi1')
path.write_text(text,encoding='utf-8',newline='\n')
path=payload/'wechat_gui_qt.py';text=path.read_text(encoding='utf-8')
text=text.replace('def sync_ui_scale(width: int, height: int, *roots: QWidget | None) -> None:',
'def sync_ui_scale(width: int, height: int, *roots: QWidget | None, factor: float | None = None) -> None:')
text=text.replace(' factor = _scale_from_window(width, height)\n trees',
' factor = _scale_from_window(width, height) if factor is None else factor\n trees')
text=text.replace(' """Scale fonts and layout tokens from the 1630×920 design canvas."""',
' """Replay design metrics; the desktop supplies a DPI-independent user scale."""')
point='def _scale_from_window(width: int, height: int) -> float:'
helper='''def interface_scale(preference: str = "自动") -> float:
"""Relative UI size, independent of the monitor's automatic DPI scaling."""
return {"100%": 1.0, "125%": 1.25, "150%": 1.5,
"200%": 2.0, "250%": 2.5, "300%": 3.0}.get(str(preference), 1.0)
'''
text=text.replace(point,helper+point)
text=text.replace('("自动", "100%", "125%", "150%", "200%")','("自动", "100%", "125%", "150%", "200%", "250%", "300%")')
text=text.replace('"▣", "缩放比例",','"▣", "界面大小",')
text=text.replace('settings.setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, False)',
'settings.setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, True)')
old=''' self.setWindowTitle(WIN_TITLE)
self.window_bar = DesktopWindowBar(self)'''
new=''' self.setWindowTitle(WIN_TITLE)
self.runtime_settings = self._load_runtime_settings()
sync_ui_scale(0, 0, factor=interface_scale(self.runtime_settings.get("scale_ratio", "自动")))
self.window_bar = DesktopWindowBar(self)'''
assert text.count(old)==1;text=text.replace(old,new)
text=text.replace(' self.setMinimumSize(980, 640)\n',' self.setMinimumSize(480, 320)\n')
text=text.replace(' sync_ui_scale(self.width(), self.height())\n','')
text=text.replace(' self._capsule_minimize_pending = False\n self.runtime_settings = self._load_runtime_settings()',
' self._capsule_minimize_pending = False')
start=text.index(' def _set_initial_geometry(self) -> None:');end=text.index(' def resizeEvent(self, event)',start)
text=text[:start]+''' def _set_initial_geometry(self) -> None:
screen = self.screen() or QApplication.primaryScreen()
area = screen.availableGeometry() if screen else QRect(0, 0, 1920, 1080)
width = max(1, min(1630, area.width() - 32))
height = max(1, min(920, area.height() - 32))
self.setMinimumSize(min(480, width), min(320, height))
self.resize(width, height)
self.move(area.x() + (area.width() - width) // 2, area.y() + (area.height() - height) // 2)
def _apply_window_ui_scale(self) -> None:
sync_ui_scale(
self.width(), self.height(),
getattr(self, "_legacy_host", None),
getattr(self, "capsule", None),
getattr(self, "window_bar", None),
factor=interface_scale(self.runtime_settings.get("scale_ratio", "自动")),
)
def showEvent(self, event) -> None:
super().showEvent(event)
handle = self.windowHandle()
if handle is not None and not getattr(self, "_screen_hooked", False):
handle.screenChanged.connect(self._screen_changed)
self._screen_hooked = True
self._screen_changed(handle.screen())
def _screen_changed(self, screen) -> None:
previous = getattr(self, "_watched_screen", None)
if previous is not screen:
if previous is not None:
for signal in (previous.availableGeometryChanged, previous.logicalDotsPerInchChanged):
try:
signal.disconnect(self._display_metrics_changed)
except (RuntimeError, TypeError):
pass
self._watched_screen = screen
if screen is not None:
screen.availableGeometryChanged.connect(self._display_metrics_changed)
screen.logicalDotsPerInchChanged.connect(self._display_metrics_changed)
QTimer.singleShot(0, self._display_metrics_changed)
def _display_metrics_changed(self, *_args) -> None:
screen = self.screen()
if screen is None:
return
area = screen.availableGeometry()
self.setMinimumSize(min(480, max(1, area.width())), min(320, max(1, area.height())))
if not (self.isMaximized() or self.isFullScreen() or self.isMinimized()):
rect = self.geometry()
width, height = min(rect.width(), area.width()), min(rect.height(), area.height())
x = max(area.x(), min(rect.x(), area.x() + area.width() - width))
y = max(area.y(), min(rect.y(), area.y() + area.height() - height))
self.setGeometry(x, y, width, height)
self._scale_timer.start()
shell = getattr(self, "console_shell", None)
if shell is not None and shell.page_ready:
shell.page().runJavaScript("if (window.fit) fit();")
''' + text[end:]
old=' self._broadcast_save_result(True, "设置已保存并生效")'
new=''' if hasattr(self, "_scale_timer"):
self._scale_timer.start()
self._broadcast_save_result(True, "设置已保存并生效")'''
assert text.count(old)==1;text=text.replace(old,new)
start=text.index('def _apply_saved_scale_preference() -> None:');end=text.index('def main() -> None:',start)
text=text[:start]+text[end:]
text=text.replace(' _apply_saved_scale_preference()\n','')
ast.parse(text);path.write_text(text,encoding='utf-8',newline='\n')
print('Prepared DPI patch for preview')