Skip to content

Commit 00ee32c

Browse files
committed
fix: capture OLED screenshot on Failure responses
Extract screenshot capture into _capture_oled() helper method. Call it from callback_ButtonRequest (existing) AND on Failure responses in call_raw. This captures rejection screens like invalid BIP-39 word during cipher recovery.
1 parent 736fb56 commit 00ee32c

1 file changed

Lines changed: 34 additions & 26 deletions

File tree

keepkeylib/client.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,11 @@ def call_raw(self, msg):
434434

435435
# Screenshot capture disabled in call_raw (captures idle screens, adds latency).
436436
# Real confirmation screenshots are captured in callback_ButtonRequest instead.
437+
# Exception: capture on Failure (rejection screens like invalid BIP-39 word).
437438

438439
resp = super(DebugLinkMixin, self).call_raw(msg)
440+
if isinstance(resp, proto.Failure):
441+
self._capture_oled()
439442
self._check_request(resp)
440443
return resp
441444

@@ -457,37 +460,42 @@ def _check_request(self, msg):
457460
raise CallException(types.Failure_Other,
458461
"Expected %s, got %s" % (pprint(expected), pprint(msg)))
459462

463+
def _capture_oled(self):
464+
"""Capture current OLED layout to screenshot directory."""
465+
if not SCREENSHOT or not self.debug:
466+
return
467+
try:
468+
layout = self.debug.read_layout()
469+
if layout and len(layout) >= 1024:
470+
layout_bytes = len(layout)
471+
height = 64 if layout_bytes >= 2048 else 32
472+
rows = []
473+
for y in range(height):
474+
row = bytearray(256)
475+
for x in range(256):
476+
byte_idx = x + (y // 8) * 256
477+
if byte_idx < layout_bytes:
478+
b = layout[byte_idx] if isinstance(layout[byte_idx], int) else ord(layout[byte_idx])
479+
if (b >> (y % 8)) & 1:
480+
row[x] = 255
481+
rows.append(bytes(row))
482+
while len(rows) < 64:
483+
rows.append(bytes(256))
484+
screenshot_dir = getattr(self, 'screenshot_dir', os.environ.get('SCREENSHOT_DIR', '.'))
485+
os.makedirs(screenshot_dir, exist_ok=True)
486+
png_path = os.path.join(screenshot_dir, 'btn%05d.png' % self.screenshot_id)
487+
with open(png_path, 'wb') as f:
488+
f.write(_write_png(png_path, 256, 64, rows))
489+
self.screenshot_id += 1
490+
except Exception:
491+
pass
492+
460493
def callback_ButtonRequest(self, msg):
461494
if self.verbose:
462495
log("ButtonRequest code: " + get_buttonrequest_value(msg.code))
463496

464497
# Capture OLED screenshot BEFORE pressing button (confirmation screen)
465-
if SCREENSHOT and self.debug:
466-
try:
467-
layout = self.debug.read_layout()
468-
if layout and len(layout) >= 1024:
469-
layout_bytes = len(layout)
470-
height = 64 if layout_bytes >= 2048 else 32
471-
rows = []
472-
for y in range(height):
473-
row = bytearray(256)
474-
for x in range(256):
475-
byte_idx = x + (y // 8) * 256
476-
if byte_idx < layout_bytes:
477-
b = layout[byte_idx] if isinstance(layout[byte_idx], int) else ord(layout[byte_idx])
478-
if (b >> (y % 8)) & 1:
479-
row[x] = 255
480-
rows.append(bytes(row))
481-
while len(rows) < 64:
482-
rows.append(bytes(256))
483-
screenshot_dir = getattr(self, 'screenshot_dir', os.environ.get('SCREENSHOT_DIR', '.'))
484-
os.makedirs(screenshot_dir, exist_ok=True)
485-
png_path = os.path.join(screenshot_dir, 'btn%05d.png' % self.screenshot_id)
486-
with open(png_path, 'wb') as f:
487-
f.write(_write_png(png_path, 256, 64, rows))
488-
self.screenshot_id += 1
489-
except Exception:
490-
pass
498+
self._capture_oled()
491499

492500
if self.auto_button:
493501
if self.verbose:

0 commit comments

Comments
 (0)