Skip to main content
rb.overlay draws toasts, menus, text prompts, status strips, progress bars and surfaces you lay out yourself. A script reaches for it to show its state, or to ask for a choice or a line of text, without taking focus from the app in front. They show over every app on Windows and macOS, including full-screen ones, except a Windows game in exclusive full-screen mode (see Overlay limits). Overlays never take focus or clicks, so the app underneath keeps working. Every call returns at once; Rebind draws the latest state. Needs: min_sdk=3.11.1. This script counts F7 presses on a strip at the top of the screen and toasts the count on F8:
Widget handles take a dot (status.set(text)); a surface takes a colon (surface:set(fields)). The other form fails or draws the wrong text. A script’s overlays are removed when it stops, errors or reloads, and hidden while it is inactive because focus left its target window.

Themes and text

Every widget takes theme: "rebind" (the default), "rebind-light", "catppuccin", "tokyo-night" or "contrast", or a table with the same roles. Text is the bundled JetBrains Mono, so columns line up. Text is a string (a newline starts a line) or a list of spans, each a string or { text, color, bold }. A color is "#rrggbb", "#rrggbbaa" or a theme role: "text", "dim", "faint", "accent", "positive", "warning" or "danger". This script shows a toast at start in the tokyo-night theme, with its first span bold in the warning role:

Placement

Widgets and surfaces take an anchor: "top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom" or "bottom-right", inside the work area of a display: "active" (default, the monitor holding the foreground window), "primary" or "mouse". offset_x and offset_y move it inward. An anchored overlay follows the foreground window to another monitor within half a second. Alternatively, x and y place the top-left corner in rb.screen coordinates. Sizes are logical units, scaled for each monitor.

rb.overlay.toast

Shows for duration milliseconds (default 4000). Options: title and meta add a heading line; max_width caps the width (default 640). Toasts at the same anchor stack. A toast asked for while visible() is false waits until overlays show, or gives up after expires milliseconds (default 60000).

rb.overlay.menu

While open, the menu takes Up, Down, Enter, Escape and 1 to 9, and swallows them so the app underneath does not see them. Enter or a number picks a row: the menu closes first unless keep_open is set, then the item’s action(index, item) runs, then on_select(index, item). Escape closes it, and so does the script losing focus to a window it does not target or the OS hiding overlays (checked every 500 ms). on_close runs when the menu closes in any of those ways or by close(); rb.overlay.clear() and the script stopping close it without on_close. Items are strings or { label, hint, action }; hint shows on the right. Options: title, subtitle, width. The handle’s select(index) moves the highlight and close() closes it. One menu or prompt across all scripts holds the keyboard at a time. rb.overlay.menu returns nil, "a menu is already open" while this script has one open, nil, "another menu is open" while another script’s menu or prompt is open or while this script is unfocused, and nil, "overlays are hidden" while visible() is false. A bind the script made earlier on one of the menu’s keys, and that swallows it, takes the key before the menu does. This script opens a two-row menu on F8; picking Start farming logs start:

rb.overlay.prompt

Needs: min_sdk=3.15.0. rb.overlay.prompt shows a one-line text field, for the kind of answer a file-name dialog or a typed setting would ask for. It types through swallowing binds, as the menu does, so it takes no focus and the app underneath keeps its mouse and every key the prompt does not take. While open, the prompt takes every letter, digit and punctuation key, the numpad keys that type a character (digits, /, *, -, + and ., whatever the Num Lock state), Space, Backspace, Enter, numpad Enter and Escape, and swallows them. Each key types its US-layout character, and Shift picks the shifted one. Ctrl+V pastes the clipboard (Cmd+V as well on macOS): control characters become spaces, the text stops at max_length, and an empty clipboard does nothing. In a script that declares permissions, pasting needs the clipboard:read permission; without it the chord does nothing. Ctrl, Alt and Win chords on any other key reach the app. Enter closes the prompt, then runs on_submit(text). Escape, close(), the script losing focus to a window it does not target, and the OS hiding overlays close it and run on_cancel(). on_close() then runs after either. rb.overlay.clear() and the script stopping close it without running any of them. Options: title, value (the starting text), placeholder (shown while the field is empty), max_length (characters, 1 to 256, default 128), width (default 360, or wider to fit the label and title). The handle’s text holds what was typed, and set(text) replaces it; value and set turn control characters into spaces and cut the text at max_length, as paste does. When the text is wider than the field, the field shows its end. A prompt counts as a menu for the reasons above: it returns nil, "a menu is already open", nil, "another menu is open" or nil, "overlays are hidden" under the same conditions, and a menu cannot open while a prompt is open. An error raised inside on_submit lets the Enter press through to the app, as for a menu pick. Limits:
  • Key names are physical keys, so a layout other than US types the US character for each key. Paste is the way to enter other layouts and Unicode.
  • Caps Lock is not read. There is no input method editor and no mouse caret; typing and Backspace act at the end of the text.
  • Rebind does not dispatch key repeat to binds, so holding a key types or deletes once.
  • A bind the script made earlier on one of these keys, or a bind or blocking handler in a script with a higher z_index, takes the key before the prompt does.
This script asks for a clip name on F8 and toasts the name when the user presses Enter:

rb.overlay.surface

Draws a list of ops you lay out yourself, in logical units from the surface’s top-left corner. width and height are required (1 to 4096, and at most 1,000,000 square units together); opacity is 0 to 1; capturable is described under Overlay limits. Ops:
  • { kind = "rect", x, y, w, h, fill, border, border_width, radius, shadow }; shadow draws a soft shadow 8 units below, blurred over 24: true is 50% black, or give its opacity from 0 to 1. Each theme’s shadow holds the opacity its widgets use.
  • { kind = "text", x, y, size, text, color, bold } or { kind = "text", x, y, size, spans }; y is the top of the line, size 6 to 96 (default 14).
  • { kind = "image", x, y, w, h, png }; png is the bytes of a PNG file, for example rb.file.read_bytes("icon.png"). w and h default to the image’s size in pixels. A PNG that fails to decode draws nothing. An image op needs min_sdk=3.14.0.
set(fields) merges fields into the surface and redraws; draw(ops) replaces the ops. A script may have 16 surfaces at once, covering at most 2,000,000 square units together, with 256 ops, 4,096 bytes of text, 1,048,576 bytes of PNG and 4,194,304 image pixels (2048 by 2048) in each. Invalid options raise a message starting rb.overlay:. A toast that waited for hidden overlays and then cannot show is dropped with a warning in the script’s log.

Overlay limits

  • A game in exclusive full-screen mode on Windows hides overlays; visible() returns false then. Borderless and windowed games show them. On macOS visible() turns false only after the overlays on screen have been covered for 2 seconds; with none on screen it returns true.
  • Overlays stay out of screenshots and recordings on Windows, and out of screenshots on macOS; macOS screen recordings may include them. A widget or surface given capturable = true shows in screenshots and recordings on both; capturable needs min_sdk=3.14.0.
  • Screen reads under an overlay that is not capturable return what is beneath it. On macOS, every script’s reads under a capturable one include its pixels, and a read under any overlay takes 10 to 25 ms, so a script should not draw over a region it reads every tick.