1#!/usr/bin/env python3
2"""Capture a native page at 100% in a disposable clone held by native_runner.py --inspect."""
3import argparse
4import base64
5import hashlib
6import json
7from pathlib import Path
8import re
9import sys
10import xml.etree.ElementTree as ET
11
12ROOT = Path(__file__).resolve().parents[2]
13sys.path.insert(0, str(ROOT / 'tools/w7'))
14import mcp_win7 as windows
15
16
17def capture(target, page, output, scrolls):
18 machine = json.loads((page.parent.parent / 'machine.json').read_text())
19 if machine['name'] != target:
20 raise ValueError('The native capture belongs to a different disposable clone')
21 page_id = ET.parse(page).getroot().attrib['ID']
22 if not re.fullmatch(r'\{[0-9A-Fa-f-]+\}\{[0-9]+\}\{[0-9A-Fa-f]+\}', page_id):
23 raise ValueError('Invalid native page identity')
24 if not 0 <= scrolls <= 20:
25 raise ValueError('Choose between zero and twenty scroll steps')
26 output.mkdir(parents=True, exist_ok=False)
27 setup = '''
28dm := Buffer(220, 0)
29NumPut("UShort", 220, dm, 68)
30if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", -1, "Ptr", dm)
31 throw Error("Unable to read display mode")
32if NumGet(dm, 172, "UInt") != 1600 || NumGet(dm, 176, "UInt") != 900 {
33 NumPut("UInt", 0x180000, dm, 72)
34 NumPut("UInt", 1600, dm, 172)
35 NumPut("UInt", 900, dm, 176)
36 result := DllCall("ChangeDisplaySettingsW", "Ptr", dm, "UInt", 0, "Int")
37 if result != 0
38 throw Error("Display mode rejected: " result)
39}
40Sleep(300)
41app := ComObject("OneNote.Application")
42'''
43 setup += f'app.NavigateTo("{page_id}", "", false)\n'
44 setup += '''
45hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
46WinMaximize(hwnd)
47WinActivate(hwnd)
48WinWaitActive(hwnd,, 10)
49Send("{Esc}")
50Sleep(200)
51Send("!w")
52Sleep(200)
53Send("q")
54Sleep(100)
55Send("^a")
56SendText("100%")
57Send("{Enter}{Esc}")
58Sleep(200)
59Send("!w")
60dc := DllCall("GetDC", "Ptr", 0, "Ptr")
61FileAppend("dpi=" DllCall("GetDeviceCaps", "Ptr", dc, "Int", 88) "," DllCall("GetDeviceCaps", "Ptr", dc, "Int", 90) "`n", "*")
62DllCall("ReleaseDC", "Ptr", 0, "Ptr", dc)
63FileAppend("screen=" SysGet(0) "x" SysGet(1) "`n", "*")
64'''
65 steps = [('zoom', setup), ('top', '''
66Send("{Esc}{Esc}")
67WinActivate("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE")
68MouseMove(1000, 400)
69Send("{WheelUp 100}")
70MouseMove(800, 12)
71''')]
72 steps += [(f'scroll-{index + 1}', '''
73MouseMove(1000, 400)
74Send("{WheelDown 8}")
75MouseMove(800, 12)
76''') for index in range(scrolls)]
77 records = []
78 for name, script in steps:
79 script = 'OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))\n' + script
80 if name != 'zoom':
81 script += '\nhwnd := WinExist("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE")\nDllCall("RedrawWindow", "Ptr", hwnd, "Ptr", 0, "Ptr", 0, "UInt", 0x585)\n'
82 (output / f'{name}.ahk').write_text(script)
83 result = windows.do_exec(script, target=target, timeout_ms=30000, shot_delay_ms=700)
84 record = {key: value for key, value in result.items() if key != 'png_b64'}
85 (output / f'{name}.json').write_text(json.dumps(record, indent=2) + '\n')
86 if result.get('error') or result.get('exit') != 0 or not result.get('png_b64'):
87 raise RuntimeError(f'Native capture failed: {name}; inspect its JSON record')
88 data = base64.b64decode(result['png_b64'], validate=True)
89 (output / f'{name}.png').write_bytes(data)
90 records.append({'frame': name, 'sha256': hashlib.sha256(data).hexdigest()})
91 (output / 'manifest.json').write_text(json.dumps({
92 'target': target, 'source_xml_name': page.name, 'source_page_id': page_id,
93 'source_sha256': hashlib.sha256(page.read_bytes()).hexdigest(),
94 'zoom_percent': 100, 'screen': [1600, 900], 'wheel_notches_per_step': 8,
95 'frames': records,
96 }, indent=2) + '\n')
97
98
99if __name__ == '__main__':
100 parser = argparse.ArgumentParser(description=__doc__)
101 parser.add_argument('target')
102 parser.add_argument('page_xml', type=Path)
103 parser.add_argument('output', type=Path)
104 parser.add_argument('--scrolls', type=int, default=0)
105 args = parser.parse_args()
106 capture(args.target, args.page_xml, args.output, args.scrolls)