147 lines
8.3 KiB
Python
147 lines
8.3 KiB
Python
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')
|