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.

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.

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, running shell commands, and registering entirely new window-tracking backends for compositors or desktop environments that HPR doesn't natively support.

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.

📁

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.

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\

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, clean up system hooks, and disconnect Event Hub subscriptions to prevent memory leaks.

A minimal extension that just prints the active app every tick looks like this:

hello.lua
function onTick(delta)
    print(HPR.getCurrentWindow_E())
end

Native Extensions (.dll / .so)

🛑 WARNING: EXPERTS ONLY

This feature is strictly intended for advanced C++ programmers or developers who fully accept the risk of system instability. If you do not have extensive experience compiling low-level systems, or if you care about the absolute security and stability of your HPR tracking app, DO NOT USE NATIVE EXTENSIONS. Stick to standard Lua extensions.

⚠️ Critical Security & Sandbox Warning

Unlike standard Lua extensions which execute safely inside a restricted, sandboxed virtual machine, native extensions run as raw compiled code directly on your host operating system.

What this means: Native extensions completely bypass ALL Lua sandboxing, dangerous command filtering, system hook protections, and VM isolation. They have absolute read/write access to your system memory, files, and network. **Only enable this feature and load native binaries if you fully trust their origin and have reviewed their source code.**

Enabling Native Extension Support

By default, HPR blocks native extensions. If a binary is detected but the feature is disabled, HPR will log a warning to the console on startup and skip loading it:

[HPR] Native extension found but allow-dynamic-library-extensions is false. Skipping: /path/to/extension.so

To enable native extension loading, modify your config.csv (located in the HPR configuration directory) and add or modify the following line:

config.csv
allow-dynamic-library-extensions, true

The Exported C Interface

Because there is no official pre-built documentation or static library for the C++ extension API, native extensions must be written to export traditional extern "C" symbols that HPR resolves dynamically at runtime.

To build a native extension, your code must export the following three main lifecycle functions:

extern "C" int init()

Called exactly once on startup when the library is loaded. Must return an integer representing the desired **tick interval/sleep time** in milliseconds. If omitted or not exported, HPR defaults the thread sleep interval to 1000ms.

extern "C" void onTick(float delta)

Called repeatedly inside a dedicated background thread. Receives a single float delta representing the actual elapsed time in milliseconds since the previous tick loop.

extern "C" void onExit()

Called exactly once when HPR shuts down or stops the extension. Use this hook to free allocated memory, release system resources, flush file streams, or gracefully close network connections to avoid memory leaks.

Below is a minimal, fully-conforming C++ template for a native extension. You can build this into a shared object (.so / .dylib) or dynamic-link library (.dll):

extension.cpp
#include <iostream>

extern "C" {
    // Called once on initialization. Sets the tick interval to 500ms.
    int init() {
        std::cout << "[Native] Extension loaded!" << std::endl;
        return 500; 
    }

    // Called every 500ms. Receives actual delta time in milliseconds.
    void onTick(float delta) {
        std::cout << "[Native] Tick delta: " << delta << "ms" << std::endl;
    }

    // Called on shutdown. Clean up resources here.
    void onExit() {
        std::cout << "[Native] Shutting down..." << std::endl;
    }
}

Linking & Source Code Reference

Since HPR does not distribute a pre-compiled SDK or stable public headers for native development, there is no formal linkage protocol.

To write and link a stable native extension, developers must read the general HPR Source Code (not just the extension manager files) to explore how internal structures, database handlers, and APIs are managed, and link against the application's targets.

To understand the basic dynamic loader lifecycle and thread execution details, see the src/extension/extensionManager.cpp implementation directly.

Tutorials & Reference

Comprehensive guides and references for writing custom HPR extensions.

Building a Custom Window Backend

Learn how to use HPR.registerBackend_E() to add tracking support for any compositor or desktop environment. Covers all 6 required functions 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 →

Raw Extension API Reference

Detailed reference for the global HPR table functions, including alias lookup, reverse-alias lookup, and lifecycle tick configuration.

View API Reference →