Feature Request: Add UTF-8 byte counter to the message input field #114

Closed
opened 2026-10-27 13:43:04 +01:00 by adam · 3 comments
Owner

Currently, there is no visual indicator showing how close a user is to the Meshtastic message payload limit (around 220 bytes). This is especially problematic for users typing in non-ASCII languages (like Cyrillic), where a single character takes 2 bytes. Users can unknowingly exceed the payload limit before hitting Enter.

Proposed Solution:
Add a live byte counter (e.g., 0/220b) to the bottom right border of the message entry window (entry_win).

The counter should calculate the actual UTF-8 byte length (len(content.encode('utf-8'))), not just the string character count.

If the limit (> 220 bytes) is exceeded, the counter should change color (e.g., using settings_warning) to visually alert the user.

Here is a working implementation that modifies the draw_message_input function in contact/ui/contact_ui.py:

def draw_message_input(draft: str) -> None:
    """Redraw the whole input row and scroll it to keep the draft cursor visible."""
    height, width = entry_win.getmaxyx()
    prompt = slice_to_width("Message: ", max(0, width - 3))
    available = max(1, width - 2 - text_width(prompt))
    ui_state.input_cursor = max(0, min(ui_state.input_cursor, len(draft)))
    content = ui_state.reply_context + draft
    
    # Calculate bytes in UTF-8 to account for non-ASCII characters
    byte_count = len(content.encode('utf-8'))
    counter_str = f" {byte_count}/220b "

    cursor = len(ui_state.reply_context) + ui_state.input_cursor
    start = cursor
    used = 0
    while start > 0 and used + text_width(content[start - 1]) < available:
        start -= 1
        used += text_width(content[start])
    visible = slice_to_width(content[start:], available)
    draw_text_field(entry_win, prompt + visible, get_color("input"))
    paint_frame(entry_win, selected=ui_state.input_focused, refresh=False)
    
    try:
        hint = " Ctrl+K Help "
        if width >= len(hint) + 4:
            entry_win.addstr(0, width - len(hint) - 2, hint, get_color("commands"))
            
        # Highlight the counter if it exceeds the limit
        if byte_count > 220:
            try:
                counter_color = get_color("settings_warning", bold=True)
            except:
                counter_color = get_color("commands", bold=True, reverse=True)
        else:
            counter_color = get_color("commands")

        # Draw the counter on the bottom border
        if width >= len(counter_str) + 4 and height > 2:
            entry_win.addstr(height - 1, width - len(counter_str) - 2, counter_str, counter_color)

        curses.curs_set(1 if ui_state.input_focused else 0)
        entry_win.move(1, min(width - 2, 1 + text_width(prompt) + used))
    except curses.error:
        pass
    entry_win.refresh()
Currently, there is no visual indicator showing how close a user is to the Meshtastic message payload limit (around 220 bytes). This is especially problematic for users typing in non-ASCII languages (like Cyrillic), where a single character takes 2 bytes. Users can unknowingly exceed the payload limit before hitting Enter. Proposed Solution: Add a live byte counter (e.g., 0/220b) to the bottom right border of the message entry window (entry_win). The counter should calculate the actual UTF-8 byte length (len(content.encode('utf-8'))), not just the string character count. If the limit (> 220 bytes) is exceeded, the counter should change color (e.g., using settings_warning) to visually alert the user. Here is a working implementation that modifies the draw_message_input function in contact/ui/contact_ui.py: ``` def draw_message_input(draft: str) -> None: """Redraw the whole input row and scroll it to keep the draft cursor visible.""" height, width = entry_win.getmaxyx() prompt = slice_to_width("Message: ", max(0, width - 3)) available = max(1, width - 2 - text_width(prompt)) ui_state.input_cursor = max(0, min(ui_state.input_cursor, len(draft))) content = ui_state.reply_context + draft # Calculate bytes in UTF-8 to account for non-ASCII characters byte_count = len(content.encode('utf-8')) counter_str = f" {byte_count}/220b " cursor = len(ui_state.reply_context) + ui_state.input_cursor start = cursor used = 0 while start > 0 and used + text_width(content[start - 1]) < available: start -= 1 used += text_width(content[start]) visible = slice_to_width(content[start:], available) draw_text_field(entry_win, prompt + visible, get_color("input")) paint_frame(entry_win, selected=ui_state.input_focused, refresh=False) try: hint = " Ctrl+K Help " if width >= len(hint) + 4: entry_win.addstr(0, width - len(hint) - 2, hint, get_color("commands")) # Highlight the counter if it exceeds the limit if byte_count > 220: try: counter_color = get_color("settings_warning", bold=True) except: counter_color = get_color("commands", bold=True, reverse=True) else: counter_color = get_color("commands") # Draw the counter on the bottom border if width >= len(counter_str) + 4 and height > 2: entry_win.addstr(height - 1, width - len(counter_str) - 2, counter_str, counter_color) curses.curs_set(1 if ui_state.input_focused else 0) entry_win.move(1, min(width - 2, 1 + text_width(prompt) + used)) except curses.error: pass entry_win.refresh() ``` <!-- github-sync-id:5929563096 -->
adam closed this issue 2026-10-27 13:43:04 +01:00
Author
Owner

Great idea! Please submit a PR and I will test/review.

Great idea! Please submit a PR and I will test/review. <!-- github-comment-id:6275552560 -->
Author
Owner
  1. Great idea! Please submit a PR and I will test/review.

Done

> 1. Great idea! Please submit a PR and I will test/review. Done <!-- github-comment-id:6276242200 -->
Author
Owner
#320 <!-- github-comment-id:6278575519 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/contact#114