| 1 | #!/usr/bin/env python3 |
| 2 | """Capture a native page at 100% in a disposable clone held by native_runner.py --inspect.""" |
| 3 | import argparse |
| 4 | import base64 |
| 5 | import hashlib |
| 6 | import json |
| 7 | from pathlib import Path |
| 8 | import re |
| 9 | import sys |
| 10 | import xml.etree.ElementTree as ET |
| 11 | |
| 12 | ROOT = Path(__file__).resolve().parents[2] |
| 13 | sys.path.insert(0, str(ROOT / 'tools/w7')) |
| 14 | import mcp_win7 as windows |
| 15 | |
| 16 | |
| 17 | def 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 = ''' |
| 28 | dm := Buffer(220, 0) |
| 29 | NumPut("UShort", 220, dm, 68) |
| 30 | if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", -1, "Ptr", dm) |
| 31 | throw Error("Unable to read display mode") |
| 32 | if 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 | } |
| 40 | Sleep(300) |
| 41 | app := ComObject("OneNote.Application") |
| 42 | ''' |
| 43 | setup += f'app.NavigateTo("{page_id}", "", false)\n' |
| 44 | setup += ''' |
| 45 | hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10) |
| 46 | WinMaximize(hwnd) |
| 47 | WinActivate(hwnd) |
| 48 | WinWaitActive(hwnd,, 10) |
| 49 | Send("{Esc}") |
| 50 | Sleep(200) |
| 51 | Send("!w") |
| 52 | Sleep(200) |
| 53 | Send("q") |
| 54 | Sleep(100) |
| 55 | Send("^a") |
| 56 | SendText("100%") |
| 57 | Send("{Enter}{Esc}") |
| 58 | Sleep(200) |
| 59 | Send("!w") |
| 60 | dc := DllCall("GetDC", "Ptr", 0, "Ptr") |
| 61 | FileAppend("dpi=" DllCall("GetDeviceCaps", "Ptr", dc, "Int", 88) "," DllCall("GetDeviceCaps", "Ptr", dc, "Int", 90) "`n", "*") |
| 62 | DllCall("ReleaseDC", "Ptr", 0, "Ptr", dc) |
| 63 | FileAppend("screen=" SysGet(0) "x" SysGet(1) "`n", "*") |
| 64 | ''' |
| 65 | steps = [('zoom', setup), ('top', ''' |
| 66 | Send("{Esc}{Esc}") |
| 67 | WinActivate("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE") |
| 68 | MouseMove(1000, 400) |
| 69 | Send("{WheelUp 100}") |
| 70 | MouseMove(800, 12) |
| 71 | ''')] |
| 72 | steps += [(f'scroll-{index + 1}', ''' |
| 73 | MouseMove(1000, 400) |
| 74 | Send("{WheelDown 8}") |
| 75 | MouseMove(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 | |
| 99 | if __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) |