Skip to content

Repository files navigation

pmptr

npm version npm downloads GitHub release Build Release Pipeline

Status: beta License: MIT Platform Node Electron

pmptr is a minimal virtual teleprompter that lives as a transparent, always-on-top, click-through overlay over whatever you do on your screen.

Features

  • 🎛️ Control window - paste your script, tune speed, size, colors, opacity, mirror, window dimensions, and more.
  • 🪟 Floating prompter window - transparent, frameless, always on top, with a true OS-level click-through "lock" so you can keep working with your mouse on whatever is underneath.
  • 💾 Settings persistence - settings are saved to disk in your Electron user-data folder.
  • Live updates - edits in the control window apply to the prompter instantly.
  • 🎙️ Voice follow (opt-in) - the prompter scrolls while you speak and pauses when you stop, so the script follows your natural pace instead of forcing a monotonous rhythm. All audio processing happens locally; nothing is recorded or sent anywhere.
  • ⌨️ Keyboard shortcuts - play/pause, reset, speed control, voice follow, and click-through toggle.

Quick Start

The fastest way to try pmptr is via npm:

npm install -g pmptr
pmptr

Requires Node.js 20 or later.

Then click Open floating prompter in the control window.

Download

Beta - pmptr is in active beta. Expect occasional bugs and breaking changes. Please report issues you encounter.

Prefer a native installer? Grab the latest build from the Releases page.

Platform Format
macOS .dmg
Windows .exe (NSIS installer)
Linux .AppImage or .deb

Not code-signed - your OS may warn on first launch. That's expected during beta.

Run from source

git clone https://github.com/jatinkrmalik/pmptr.git
cd pmptr
npm install
npm start

Shortcuts (in the floating window)

Key Action
Space Play / pause
R Reset scroll to the top
/ Speed ± 5 px/s
L Toggle click-through (lock / unlock)
V Toggle voice follow
Esc Close the prompter

You can also use the small HUD in the bottom-right of the floating window (mouse over it to reveal it).

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              pmptr Architecture                             │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────────┐
                              │     Main process    │
                              │  src/main/main.js   │  Creates windows, owns
                              │ src/main/preload.js │  IPC, click-through,
                              │                     │  always-on-top, settings
                              └──────────┬──────────┘
                                         │
              ┌──────────────────────────┼──────────────────────────┐
              │ spawns                   │ spawns                   │ read/write
              ▼                          ▼                          ▼
┌─────────────────────┐      ┌─────────────────────┐      ┌──────────────────┐
│    Control window   │      │   Prompter window   │      │   settings.json  │
│ src/control/        │      │ src/prompter/       │      │  (Electron user  │
│   control.html      │      │   prompter.html     │      │   data directory)│
│   control.js        │      │   prompter.js       │      └──────────────────┘
└─────────────────────┘      │ prompter-preload.js │
                             └─────────────────────┘

Data flow
─────────

  Control window              Main process             Prompter window
        │                          │                           │
        │── IPC: setting changed ─►│                           │
        │                          │── IPC: settings ─────────►│
        │                          │                           │
        │◄──────── IPC: state ─────│◄──────── IPC: state ──────│
        │                          │                           │

- The **main process** spawns both windows and persists settings to `settings.json`.
- The **control window** sends new settings to the main process over IPC.
- The **prompter window** receives settings from the main process and reports its state back.
- Live edits in the control window are reflected in the prompter instantly.

How the click-through works

The prompter is a separate BrowserWindow with transparent: true, frame: false, and alwaysOnTop: true. When you toggle "click-through" (the lock), the main process calls win.setIgnoreMouseEvents(true, { forward: true }) - clicks and wheel events fall straight through to whatever app is behind, while the window stays visible and keeps scrolling. The HUD itself is hidden while locked, so nothing on the prompter intercepts your pointer.

How voice follow works

Voice follow is off by default; enable it with the toggle in the control window's Reading tab, the mic button in the prompter HUD, or the V key. While enabled, the prompter listens to your microphone through the Web Audio API and runs a small voice-activity detector (signal level vs. an adaptive noise floor). While you speak, the text scrolls at your configured speed; when you pause, the scroll eases to a stop after a short grace period (so natural gaps between words don't stall it) and resumes as soon as you speak again.

  • Mic sensitivity controls how loud you must be relative to background noise before scrolling starts. Raise it if the prompter misses quiet speech; lower it in noisy rooms.
  • Everything runs locally in the prompter window - no audio is recorded, stored, or sent anywhere.
  • If the microphone is unavailable, the prompter falls back to constant scrolling and the control window shows a mic error.

Tweaking transparency

The window background is a CSS rgba() color set on .frame. Move the Background opacity slider to 0 for fully see-through, or use Background dim to keep it readable on bright content underneath. The text itself stays opaque.

Project layout

src/
├── main/
│   ├── main.js               Electron main: creates both windows, handles IPC,
│   │                         click-through, always-on-top, position presets.
│   └── preload.js            contextBridge for the control window.
├── control/
│   ├── control.html          The control panel UI.
│   ├── control.js            Control panel logic.
│   └── control.css           Control panel styles.
└── prompter/
    ├── prompter.html         The floating teleprompter overlay.
    ├── prompter.js           Prompter logic (scroll, shortcuts, HUD).
    ├── prompter.css          Prompter styles.
    └── prompter-preload.js   contextBridge for the prompter window.

assets/
└── icon.svg                  Application icon.

.github/workflows/
── build.yml                 CI build for Linux, macOS, Windows.
├── release.yml               Tag-triggered release: installers, GitHub Release, npm publish.
├── nightly.yml               Daily scheduled builds.
└── pr-build.yml              Comment-triggered PR artifact builds.

Development

# Install dependencies
npm install

# Run the app
npm start

# Lint code
npm run lint

# Run tests (currently just lint)
npm test

# Build for distribution
npm run build

Known limitations

  • Wayland compositors vary in their support for setIgnoreMouseEvents and setAlwaysOnTop (the underlying APIs Electron uses). X11 (Xorg) and recent KDE / GNOME Wayland work fine; some lighter Wayland compositors may ignore these hints. If click-through or always-on-top does not work, the prompter is still useful - just drag it to a corner.
  • On macOS you may need to grant Accessibility / Screen Recording permissions to the app for click-through to behave predictably across all apps.
  • The app is not code-signed. Your OS may warn on first launch.

Contributing

Contributions are welcome! See CONTRIBUTING.md for details.

License

MIT - see LICENSE for details.

About

A minimal ad-free virtual teleprompter that lives as a transparent, always-on-top, click-through overlay over whatever you do on your screen.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages