-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhashing.py
More file actions
76 lines (63 loc) · 2.28 KB
/
Copy pathhashing.py
File metadata and controls
76 lines (63 loc) · 2.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""
hashing.py — Stable tree and snapshot hashing.
Per design doc D13: tree hash includes role, name, value, bounds, enabled.
It excludes 'focused' (changes during normal interaction without meaningful
state drift) and any timestamps. Element IDs are also excluded because they
renumber across walks; structural shape is captured via a pre-order traversal.
"""
from __future__ import annotations
import hashlib
import json
from typing import Any, Dict, Iterable, List
def tree_hash(elem: Any) -> str:
"""Return 'sha1:<hex>' over a stable serialization of *elem*."""
h = hashlib.sha1()
_feed(elem, h)
return "sha1:" + h.hexdigest()
def _feed(elem: Any, h: "hashlib._Hash") -> None:
h.update((elem.role or "").encode("utf-8"))
h.update(b"\x00")
h.update((elem.name or "").encode("utf-8"))
h.update(b"\x00")
h.update((elem.value or "").encode("utf-8"))
h.update(b"\x00")
b = elem.bounds
h.update(f"{b.x},{b.y},{b.width},{b.height}".encode("ascii"))
h.update(b"\x00")
h.update(b"E" if elem.enabled else b"D")
h.update(b"\x00")
h.update(f"{len(elem.children)}".encode("ascii"))
h.update(b"\x00")
for c in elem.children:
_feed(c, h)
def windows_hash(windows: Iterable[Any]) -> str:
"""Hash the (uid, title, bounds, focused) tuple-set of a window list."""
h = hashlib.sha1()
items: List[Dict] = []
for w in windows:
items.append({
"uid": getattr(w, "window_uid", None) or str(getattr(w, "handle", "")),
"title": w.title or "",
"process": w.process_name or "",
"bounds": w.bounds.to_dict(),
"focused": bool(w.is_focused),
})
items.sort(key=lambda x: x["uid"])
h.update(json.dumps(items, sort_keys=True).encode("utf-8"))
return "sha1:" + h.hexdigest()
def focused_selector(root: Any) -> str:
"""Return a short selector path to the focused element, or '' if none."""
found = _find_focused(root)
if not found:
return ""
from element_selectors import selector_for
sel = selector_for(root, found.element_id)
return sel or ""
def _find_focused(elem: Any) -> Any:
if elem.focused:
return elem
for c in elem.children:
r = _find_focused(c)
if r:
return r
return None