aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-24 15:42:20 +0200
committerblasty <blasty@local>2026-07-24 15:42:20 +0200
commit3835eb2aa35362401fc6489900394e022baa6981 (patch)
tree150c70cc7dbb0b0f8d7ff415f96c9cdfe7a533fc /idatui
parentdocs: refresh README for the worker-only architecture (diff)
downloadida-tui-3835eb2aa35362401fc6489900394e022baa6981.tar.gz
ida-tui-3835eb2aa35362401fc6489900394e022baa6981.tar.xz
ida-tui-3835eb2aa35362401fc6489900394e022baa6981.zip
hex: click to place the byte cursor (hex + ascii panes)
HexView subclasses ScrollView directly and had no mouse handler, so clicking never moved the byte cursor -- after a wheel-scroll the cursor was stuck off screen with no way to reposition it by mouse. Add on_click: map the content offset (scroll_offset + click, past the 1-col padding) to a row, and the x column to a byte 0..15 across both the hex cells (3 cols each, +1 gap before byte 8) and the ascii pane. Double-click jumps to code, mirroring Enter. Pilot scenario extends `hex`: scroll the viewport (cursor goes off-screen), then click in the hex pane and the ascii pane and assert the cursor lands on the exact clicked row+byte. 9/9 hex checks pass.
Diffstat (limited to 'idatui')
-rw-r--r--idatui/app.py33
1 files changed, 33 insertions, 0 deletions
diff --git a/idatui/app.py b/idatui/app.py
index 07325a1..5314f66 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -1712,6 +1712,39 @@ class HexView(ScrollView, can_focus=True):
def action_leave(self) -> None:
self.post_message(HexView.Leave())
+ # -- mouse ------------------------------------------------------------- #
+ def _byte_at_x(self, x: int) -> int:
+ """Map a content column to a byte index 0..15, across the hex and ASCII
+ panes. Matches ``render_line``'s layout: addr(9) + file-offset(10) + 16
+ hex cells of 3 cols (with a 1-col gap before byte 8), then ' |' + ASCII."""
+ HEX, ASCII = 19, 70
+ if x < HEX: # clicked the address/offset gutter -> row start
+ return 0
+ if x < HEX + 49: # hex byte region
+ rel = x - HEX
+ if rel >= 24: # collapse the 1-col gap between the two halves
+ rel -= 1
+ return min(rel // 3, 15)
+ if x < ASCII: # the ' |' separator -> last byte of the row
+ return 15
+ return min(x - ASCII, 15) # ASCII pane (and anything past it)
+
+ def on_click(self, event) -> None: # type: ignore[no-untyped-def]
+ if self.model is None or self.model.size == 0:
+ return
+ off = event.get_content_offset(self)
+ if off is None:
+ return
+ self.focus()
+ row = round(self.scroll_offset.y) + off.y
+ x = round(self.scroll_offset.x) + off.x
+ new = row * 16 + self._byte_at_x(x)
+ self.cursor = max(0, min(self.model.size - 1, new))
+ self.refresh() # cursor is reactive(repaint=False); repaint the highlight
+ self.post_message(HexView.Moved(self.cursor_va()))
+ if event.chain >= 2: # double-click == place cursor + jump to code
+ self.post_message(HexView.ToCode(self.cursor_va()))
+
# -- rendering --------------------------------------------------------- #
def _ensure_window(self, top: int) -> None:
if self.model is None: