HPR Extension Documentation
HPR ships with a built-in Lua scripting engine that lets you extend its behavior without touching a single line of C++. Write a script, drop it in a folder, and HPR loads it automatically. The API is designed to be easy for beginners — if you can write a simple function, you can build an extension. For advanced users, the Function Overriding API lets you intercept and hijack 26 core C++ functions directly from Lua.
What are HPR Extensions?
HPR extensions are plain .lua script files that you place in a specific folder on your system.
HPR automatically discovers and loads every .lua file it finds there — including files inside
subfolders — every time it starts up. No installation steps, no package managers, no compilers. If you've ever written any script in any language, you already have what it takes.
Inside your script, you have access to a global object called HPR. This object exposes a set of
functions that let your script communicate with HPR's internals — reading the currently active window, querying
databases, making HTTP requests, subscribing to system events, and even registering entirely new window-tracking backends for compositors or desktop environments
that HPR doesn't natively support.
Two levels of power: The standard Extension API gives you read/write access to HPR's tracking data, UI, events, and networking — perfect for building widgets, dashboards, and integrations. For those who want to go deeper, the Function Overriding API lets you intercept and replace 26 core C++ engine functions directly from Lua — rename windows before they're stored, mock database queries, block notifications, spoof timestamps, hijack HTTP requests, and more.
Extensions are designed to be small and focused. The most powerful extension currently possible — registering a full custom window detection backend — takes fewer than 30 lines of Lua. You do not need to be an experienced programmer to write one. If you know what terminal command gets the active window on your system, you already know 90% of what you need. Start with the QuickStart Guide and you'll have your first extension running in under 5 minutes.
💡 Preferring Extension Backends over Native: If you register a custom window tracking backend for a compositor or desktop environment that is already natively supported by HPR (like Hyprland, GNOME, KDE, Cinnamon, niri, or Windows), your Lua extension's implementation will be preferred and activated instead of the built-in C++ one. This is because HPR searches registered backends in reverse order, giving your newly loaded Lua extension priority.
Drop a file, done
Extensions are just .lua files. Place them in your HPR extensions folder and they load
automatically on the next launch. No configuration, no registration, no installation steps. HPR scans the
folder recursively so you can organize extensions into subfolders however you like.
Each extension runs on its own thread
HPR spawns a dedicated background thread for every loaded extension. This means a slow or misbehaving extension cannot block HPR's tracking loop, freeze the UI, or affect any other extension. Each extension is completely isolated from the others and runs independently at its own pace.
Sandboxed Lua environment
Extensions only have access to the Lua standard libraries that HPR explicitly opens — base,
string, and table. File I/O, OS execution, and package loading are not available
directly. Any system interaction goes through HPR's own controlled API functions, keeping the extension
environment predictable and safe.
Live memory access, no HTTP overhead
When your extension calls HPR.getCurrentWindow_E(), it reads directly from HPR's in-memory
state
through a mutex-protected C++ lambda. There is no serialization, no network stack, no JSON parsing. The
call is nanoseconds. Compare this to ActivityWatch where every data read is a full HTTP round-trip to a
localhost server.
C++ RAII Auto-Disconnection
EventHub subscriptions are bound to C++ connection tracking handles. They disconnect automatically when extensions are reloaded or unloaded, completely eliminating the risk of dangling zombie references or use-after-free crashes.
Interactive Callback Forwarding
Register UI callbacks using registerUiCallback_E. Slint callback arguments (Strings, Doubles, Booleans, Arrays, and Structs) are recursively mapped and forwarded directly to Lua VM event listeners as parameters (with a void return in C++).
Function Overriding
Completely intercept and hijack core C++ engine routines directly in Lua via the HPR.overrides table. Override active window checks, spoof databases, intercept HTTP requests, or filter UI properties dynamically on the fly.
Where to Put Your Extensions
HPR looks for extensions in a specific folder depending on your operating system. The folder is created automatically the first time HPR launches — you don't need to create it yourself.
🐧 Linux
~/.config/HPR/extensions/
🪟 Windows
%APPDATA%\HPR\HPR_Config\extensions\
Built to Survive
The extension system has been stress tested under conditions far beyond anything a real user would encounter. These are not synthetic benchmarks — they are real runs, on real hardware, with real Lua VMs, real threads, and real SQLite writes.
100 Extensions
With 100 simultaneously loaded extensions — each running its own Lua VM, its
own OS thread, its own
SQLite table receiving continuous writes, and its own event subscriptions firing every
100–500ms
— HPR consumed 5.5% CPU and approximately 53 MB of real memory. The database
absorbed over 600 MB of writes in under two minutes without a single corrupted row. The UI
remained fully responsive throughout. All 100 extensions loaded, ran, and shut down
cleanly.
1000 Extensions
HPR started in under 300ms, loaded all 1000 Lua VMs,
spawned all 1000 threads, and settled at 5.6% CPU — essentially identical to
the 100 extension result.
Real memory usage was approximately 158 MB total for all VMs, threads, and database handles
combined. The database received over 2 GB of writes and came up clean on the next launch,
validating WAL mode integrity even under a hard process kill. 1 extension out
of 1000 exceeded the
200ms shutdown grace period and was detached gracefully.
What this means for you
In a realistic deployment with 5–10 extensions, CPU impact is unmeasurable, memory overhead
is under
4-5 MB, and startup time for extension loading is under 10ms. The
1000 extension test
represents roughly 100–200× the load of a heavy real-world setup. HPR passed it without
instability, corruption, or UI degradation.
You can organize extensions into subfolders. HPR uses recursive directory scanning so a structure like this works perfectly:
extensions/
sway-backend/
sway.lua
niri-backend/
niri.lua
my-custom-thing.lua
All three of those extensions will be discovered and loaded. Each gets its own isolated Lua VM and its own background thread.
Extension Lifecycle Hooks
HPR calls specific Lua functions in your script at specific points during its lifecycle. You don't have to define all of them — HPR checks whether each function exists before calling it, so you only implement
init()
Called exactly once when HPR starts and loads your extension, before the tick loop begins. This is where
you should do any one-time setup — registering a backend, establishing connections, or printing messages.
Optionally, you can return an integer from init() to set a custom tick rate (in milliseconds)
for your extension thread (defaults to 1000ms).
onTick(delta)
Called periodically on your extension's isolated background thread (at the rate defined in
init()).
It receives a single parameter delta (a float representing actual elapsed milliseconds since
the last tick).
This is where you put recurring logic — reading active windows, logging info, or firing notifications.
onExit()
Called exactly once when HPR shuts down or when your extension is stopped. Use this hook to perform safety
flushes of database caches and clean up system hooks (HPR's RAII architecture automatically disconnects all active Event Hub subscriptions upon exit to prevent crashes and segfaults).
Strict 200ms Exit Budget:
HPR features an intentional shutdown safety timer. Every extension thread is granted a strict 200
milliseconds grace period to complete its onExit() logic. If your code takes longer than
200ms, HPR will forcefully detach the extension thread (calling ext->thread.detach()) and
immediately terminate the entire HPR application using the C system call _exit(0) to prevent
background freezes. Because _exit(0) terminates HPR instantly, it will cut off all
other running extension threads before they can finish their own cleanups! Keep your
onExit() logic highly optimized and extremely fast.
💡 Best Practice: Registering Extension Identity
HPR supports registering your extension's name and author so it displays nicely in the Loaded
Extensions manager UI. To do this, simply set the global HPR.authorName and
HPR.extensionName properties.
IMPORTANT: Declare at the Top Level!
These properties must be defined at the top level of your script (outside of
init() or any other lifecycle function). HPR inspects the Lua VM immediately after parsing the
file, before it spawns the dedicated background thread or executes init().
If you place them inside init() or skip them entirely, HPR will still load and run your extension successfully, but it will show up in the Extensions manager UI under a randomly generated name. This makes it much harder to identify which extension is which when you want to reload or disable them.
Declaring these metadata properties is not strictly required for your script logic to execute, but
it is EXTREMELY HIGHLY RECOMMENDED.
A minimal extension that just prints the active app every tick looks like this:
function onTick(delta) print(HPR.getCurrentWindow_E()) end
Tutorials & Reference
Comprehensive guides and references for writing custom HPR extensions. Start with the QuickStart guide if you're new — the tutorials are ordered from beginner-friendly to advanced.
New to building extensions for HPR? We highly recommend starting with our new QuickStart Guide. It contains step-by-step instructions for absolute beginners, including how to configure your first Lua file. Additionally, make sure to read the Common Mistakes section at the bottom of the QuickStart page to learn about crucial execution budgets, variable scoping rules, and event listener safety to ensure your extensions do not cause system crashes or segfaults.
Building a Custom Window Backend
Learn how to use HPR.registerBackend_E() to add tracking support for any compositor or
desktop.
Covers all 7 arguments with a real working example using Hyprland.
Active Media Monitoring & Spotify Tracker
Construct a rich media logging extension from the ground up. Track Spotify playback state through window details, structure high-performance SQL databases, and build a beautiful, interactive Slint tracks listing panel.
Read the tutorial →Event Hub & Burnout Detector
Learn how to use HPR's Event Hub to make isolated extensions talk to each other. Build a Focus Shield with a watcher that monitors your work and an enforcer that triggers native OS notifications.
Read the tutorial →AW Parasite Extension Tutorial
Learn how to utilize HPR's native network clients and JSON parser functions. Build a parasitic browser activity tracker that imports full active URLs and displays them in Slint.
Read the tutorial →⚡ Function Overriding API Advanced
For advanced users who want maximum control: intercept and override 26 core C++ engine functions directly from Lua. Rename windows before they hit the database, mock SQL query results, block desktop notifications, spoof system time, hijack HTTP requests, control tracking state, and filter UI updates. Every function documented with a working Lua code example.
View Overrides API →Raw Extension API Reference
Complete reference for all functions in the global HPR table — window queries, database access, HTTP networking, event subscriptions, UI manipulation, alias lookups, and lifecycle configuration. The standard API is beginner-friendly and covers most use cases.