Under Active Development

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

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.

10³

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:

hello.lua
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.

🚀 QuickStart Guide & Common Mistakes

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.

Read the tutorial →

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.

View API Reference →