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('')==1 text=text.replace('',' \n') 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')