Raw Extension API Reference
HPR embeds a powerful C++ Lua engine. This document describes the raw lifecycle hooks and all functions
exposed to the Lua namespace through the global HPR table.
Lifecycle Hooks
Your Lua extensions are driven by two main entry points executed on a dedicated background thread spawned specifically for your script.
Called exactly once by HPR when the extension is loaded at startup. This is the place to perform one-time initialization, such as registering a window-tracking backend.
You can return an integer value (in milliseconds) from init(). This sets the tick interval
(sleep duration) for your extension thread. If no value is returned or if the hook is omitted, the tick
rate defaults to 1000 ms (1 second).
function init() print("Initializing extension...") return 500 -- configure this extension thread to tick every 500 milliseconds end
Called periodically on your extension's isolated background thread based on the tick interval configured
in init().
HPR passes a single argument delta (a float) representing the actual elapsed time in
milliseconds since the last tick occurred. This allows for extremely precise timing,
rate calculation, or event tracking within your script.
function onTick(delta) print("Time since last tick: " .. delta .. " ms") end
Called exactly once by HPR when the application is closing or when the extension is being stopped. This is the optimal place to perform teardown operations, execute final database writes, and clean up active listeners.
CRITICAL: 200ms Execution Budget Limit!
HPR features a strict, intentional shutdown safety timer. Every extension thread has exactly 200
milliseconds to finish its onExit() handler. If your code takes longer than 200ms, HPR
will immediately detach the extension thread (calling ext->thread.detach()) and force close
the entire application using _exit(0) to prevent background hangs. Since
_exit(0) terminates HPR instantly, it will cut off all other running extension
threads before they can finish their cleanups! Ensure all operations inside
onExit() are extremely optimized and complete well within the 200ms budget.
function onExit() flushToDatabase() print("Extension is shutting down cleanly.") end
Triggered whenever the user clicks the yellow ACTION button on your extension's card in the Loaded Extensions manager UI.
Declare function onAction() ... end (or function HPR.onAction() ... end) in your extension script. This allows your extension to provide custom interactive actions, trigger on-demand tasks, or execute diagnostic routines directly from the Slint UI.
function onAction() print("Action button clicked from UI extension card!") HPR.showUiPopup("Extension Action", "Action routine executed successfully!", "OK", "Cancel", function(choice) end) end
The HPR Global Table
Extension Identity
HPR uses global state fields to register extension identities. Defining these properties enables HPR to track your extension details and display them cleanly in HPR's Loaded Extensions manager UI. If these are omitted, the extension will still load successfully but will show up under a randomly generated name.
Type:
string (property)
Configures the author or developer name of the loaded extension. This must be defined at the top level of your extension's script, outside of any functions.
Recommended: Declare at the top level!
HPR queries these identity properties immediately after parsing the Lua file. You must place them outside of any function blocks (such as init()). Omitting them entirely means HPR will fall back to registering the extension with a randomly generated name in the Loaded Extensions manager UI (though the extension will still load and execute normally). Declaring these is EXTREMELY HIGHLY RECOMMENDED.
HPR.authorName = "Plexescor"
Type:
string (property)
Configures the custom display name of the loaded extension. This must be defined at the top level of your extension's script, outside of any functions.
Recommended: Declare at the top level!
Must be set alongside HPR.authorName at the top level of the Lua script, outside of any function blocks. If omitted, the extension will fall back to being displayed under a randomly generated name in the Loaded Extensions manager UI. Declaring this is EXTREMELY HIGHLY RECOMMENDED.
HPR.extensionName = "My Custom HPR Extension"
Type:
table (array of strings)
Configures the array of supported HPR application versions for the loaded extension. This must be defined at the top level of your extension's script outside of any function blocks. Version strings are automatically normalized (leading v or V prefixes are stripped).
Version Compatibility & UI Warnings:
If omitted, the extension still loads, but displays a yellow warning in the Loaded Extensions manager UI: Unknown version compatibility for HPR <version>.
If specified and the current HPR version is missing, a detailed yellow warning is shown: This extension is incompatible with HPR <version>, the versions supported by the extension are: (<supported versions>).
HPR.versionSupport = {"0.9.7", "0.9.8", "0.9.5"}
Type:
boolean (property)
Configures whether API functions must be prefixed with HPR.. If set to false, API functions are registered to both the HPR namespace and the global environment namespace, allowing direct calling (e.g. log() instead of HPR.log()). Defaults to true.
CRITICAL: Metadata Properties are Unaffected!
Setting useHPRTablePrefix to false only binds API functions globally. Metadata properties (like HPR.extensionName, HPR.authorName, HPR.useLegacyAPISuffix, and HPR.versionSupport) are unaffected and must always use the HPR. namespace prefix.
HPR.useHPRTablePrefix = false
General Utilities
Core helper functions for timing, thread control, and UI state inspection available to every extension.
Returns:
uint64 (Unix timestamp in milliseconds)
Returns the current system time as a Unix timestamp in milliseconds. Useful for timestamping events, measuring elapsed durations, or seeding time-series database entries.
local now = HPR.getTime_MS() print("Current time (ms): " .. now)
Parameters: ms: int (milliseconds) — Returns: nothing
Blocks the calling extension's background thread for the specified number of milliseconds. Since every extension runs on its own isolated OS thread, this only pauses your script — HPR and all other extensions remain fully responsive. Useful for rate-limiting inside a callback or performing a one-shot delayed action without restructuring your tick loop.
Note on Long Sleeps & Exit Budget
Sleeping for long durations is absolutely fine. Internally, HPR breaks down your requested sleep time into 50ms chunks so that the thread remains cleanly interruptible on shutdown. You can safely call sleep in onExit() or anywhere else for any amount of seconds. However, keep in mind that doing so will reduce your strict exit budget from 200ms to 150ms, because up to 50ms (one chunk block) will be blown off by the sleep before the thread can proceed with shutdown logic!
HPR.sleep(2000) -- pause this extension thread for 2 seconds
Parameters: name: string (log category/prefix), message: string (content to log) — Returns: nothing
Appends a formatted message to HPR's centralized logger. The log will automatically include the category prefix specified in the name argument, appearing as [name] message. This is highly recommended over standard Lua print statements for structured, traceable debugging output in production environments.
HPR.log("MyExtension", "Initialized and active.")
Parameters: key: string — Returns: string
Retrieves the raw string value of a system configuration setting from HPR. Returns an empty string "" if the key does not exist.
local hwAccel = HPR.getSystemConfig("hardware-acceleration") print("Hardware Accel: " .. hwAccel) -- e.g. "true" or "false"
Parameters: path: string (path relative to the root of the extensions directory, not the individual extension subdirectory), key: string|number|boolean (optional) —
Returns: string|number|boolean (if key requested) OR table (key-value dictionary mapping if key omitted)
Reads and parses a CSV file located in HPR's designated extension directory (e.g. ~/.config/HPR/extensions/). Note that the path argument is resolved relative to the global extensions directory root itself, not the specific extension's subdirectory. Absolute paths or parent traversals (like ../) are blocked for security.
If a specific key is requested, returns the parsed value for that key, automatically converting boolean text ("true"/"false") and literal numbers into Lua types, and trimming whitespace for them. If the file read fails or is denied access, an empty string "" is returned.
If the key is omitted, it returns a single Lua table (dictionary) mapping all keys directly to their corresponding parsed values (e.g., { ["usershit"] = true, ["enabled"] = false }).
-- Read specific key local val = HPR.readCsv("config.csv", "enabled") -- Read whole file as a key-value dictionary local data = HPR.readCsv("config.csv") for key, value in pairs(data) do print(key .. ": " .. tostring(value)) end
Parameters: path: string (path relative to the root of the extensions directory, not the individual extension subdirectory), key: string|number|boolean, value: string|number|boolean —
Returns: boolean (success status)
Writes or updates a key-value entry in the specified CSV file. Note that the path argument is resolved relative to the global extensions directory root itself, not the specific extension's subdirectory.
Nothing is quoted in the CSV file, even strings. Automatically creates necessary subdirectories if requested. If the key already exists in the file, it performs an in-place update (upsert) to preserve the rest of the CSV entries. Returns true on success, or false on failure or access denial (printing standard error to std::cerr).
local ok = HPR.writeCsv("settings/general.csv", "volume", 95)
Parameters: path: string (path relative to the root of the extensions directory, not the individual extension subdirectory) —
Returns: boolean (true if the file was deleted successfully, false otherwise)
Deletes the specified CSV file. Note that the path argument is resolved relative to the global extensions directory root itself, not the specific extension's subdirectory.
Note on Shared Workspace Design: Please be aware that the extensions workspace is a shared environment. It operates essentially as a collaborative "wild west" by design, allowing any extension to read, modify, or delete database and configuration files created by any other extension to foster deep script integration and open data exchanges.
local deleted = HPR.deleteCsv("settings/general.csv") if deleted then print("CSV successfully removed!") end
Parameters: None —
Returns: string (the relative path from the extensions root of the calling extension's subdirectory with a trailing separator)
Returns a string containing the relative path from HPR's global extensions directory root to the calling extension's own subdirectory, falling back to an empty string if the script resides directly at the extensions root folder. Both names (getExtensionDir and getExtensionPath) reference the exact same underlying function.
-- Get the relative folder path of this specific extension local myFolder = HPR.getExtensionDir() -- Seamlessly resolve settings.csv relative to your own subfolder local settings = HPR.readCsv(myFolder .. "settings.csv")
Theme Customization
Exposes system level theme settings and available themes directly to extensions.
Parameters: None —
Returns: table (list of available theme names)
Returns a Lua list containing the names of all themes currently registered or available in HPR.
local themes = HPR.getThemeNames() for _, themeName in ipairs(themes) do print("Available theme: " .. themeName) end
Parameters: None —
Returns: string (name of the active theme)
Retrieves the name of the theme currently selected and active in the system settings.
local activeTheme = HPR.getCurrentThemeName() print("Currently active theme: " .. activeTheme)
Pattern Analyzer Insights
Exposes HPR's integrated background productivity and application usage analytics directly to extensions.
Returns: nothing
Forces the background pattern analyzer to calculate fresh, up-to-date insight reports based on current session and database logs.
Returns: string
Returns the process name or window title of the most heavily used application in the active analysis window.
Returns: string
Returns a user-friendly formatted duration representing the total amount of tracked focus time.
Returns: string
Returns the total count of context/application switches.
Returns: string
Returns the details of the longest uninterrupted focus session.
Returns: string
Returns the specific hour of the day where the user showed maximum active productive engagement.
Returns: string
Returns the application class name from which the user switched away most frequently during active tracking.
Returns: string
Returns the application class name to which the user switched most frequently during active tracking.
Live Time Tracking Data
Enables extensions to retrieve raw, thread-safe real-time session tracking logs mapping applications, browser tabs, and projects to their total active duration (in milliseconds) since midnight.
Returns: table (map of application name string to millisecond number)
Returns a key-value Lua table mapping each tracked application class name to the total active duration (in milliseconds) recorded for the current day.
local appLog = HPR.getLiveTimeLogPerApp() for app, ms in pairs(appLog) do print(app .. ": " .. ms .. " ms") end
Returns: table (map of browser tab title string to millisecond number)
Returns a key-value Lua table mapping each tracked browser tab/URL title to the total active duration (in milliseconds) recorded for the current day.
local tabLog = HPR.getLiveTimeLogPerTab() for title, ms in pairs(tabLog) do print(title .. ": " .. ms .. " ms") end
Returns: table (map of project name string to millisecond number)
Returns a key-value Lua table mapping each tracked VS Code development project directory to the total active duration (in milliseconds) recorded for the current day.
local projectLog = HPR.getLiveTimeLogPerProject() for project, ms in pairs(projectLog) do print(project .. ": " .. ms .. " ms") end
UI & Application Control
Exposes programmatic window control, allowing extensions to show, hide, or terminate the main HPR interface.
Returns: nothing
Brings the main HPR application window to the screen foreground, making it fully visible to the user.
HPR.showUi()
Parameters: propertyName: string, width: int, height: int, rawRGBABytes: string — Returns: nothing
Sets a Slint UI property of type <image> dynamically using a 32-bit RGBA raw pixel byte string. Note that this is NOT GPU accelerated and is rendered entirely on the CPU. And yes you can do what you are thinking(doom) you little guys!
-- Example: Render a 320x200 Red screen local width, height = 320, 200 local bytes = {} for i = 1, width * height do bytes[#bytes + 1] = string.char(255, 0, 0, 255) -- R, G, B, A end local buffer = table.concat(bytes) HPR.setUiImage("miscImage_S", width, height, buffer)
When the Miscellaneous Image Panel is visible and focused by the user, you can capture keyboard key-presses via the "MISC_KEY_PRESSED" event subscription. This allows extensions to receive what key the user pressed inside the panel. Special keys are automatically mapped to easy-to-use strings: "up", "down", "left", "right", and "space".
-- Example: Connect a listener to capture keyboard inputs in the panel HPR.connect("MISC_KEY_PRESSED", function(key) print("User pressed key inside Miscellaneous Panel: " .. key) end)
Captures key-release events when the user focuses the Miscellaneous Image Panel. By combining pressed and released signals, extensions can construct precise input states—such as continuous held traversal, single discrete triggers, or release events. This enables fluid, concurrent multi-key interactions (e.g. diagonal movement vector combinations or complex rendering environment exploration).
-- Example: Connect a listener to capture keyboard releases in the panel HPR.connect("MISC_KEY_RELEASED", function(key) print("User released key inside Miscellaneous Panel: " .. key) end)
Returns: nothing
Minimizes and hides the HPR application window, putting it out of focus and placing it quietly in the system tray.
HPR.hideUi()
Returns: nothing
Closes the HPR application gracefully. This signals a clean shutdown request to the HPR core loop, allowing extensions to execute their onExit() teardown functions and complete their active resource releases before the application closes.
HPR.quitUi()
Parameters: text: string, leftBtnText: string, rightBtnText: string, callback: function — Returns: nothing
Shows a custom modal popup dialog in the HPR Slint UI overlay. Clicking the left-positioned button in the UI returns 0 (integer) to the callback function, while clicking the right-positioned button returns 1 (integer). Multiple requests to show a popup are queued internally and processed sequentially. Popups automatically dismiss after a configurable timeout (default 7500 ms, read from config key extension-popup-timeout) and return -1 (integer) to the callback.
⚠️ UI Position vs Mouse Button Gotcha:
The return values 0 and 1 correspond strictly to the layout position of the buttons on the screen (the left-positioned button vs the right-positioned button). They do NOT represent mouse clicks (i.e. Left Click vs Right Click). Regardless of which mouse button the user clicks with, clicking the left-positioned UI button always returns 0, and the right-positioned UI button always returns 1.
function init() HPR.showUiPopup("Can you see this?", "No", "Yes", function(btn) if btn == 1 then HPR.log("UI", "User clicked Yes!") elseif btn == 0 then HPR.log("UI", "User clicked No!") else HPR.log("UI", "Timeout reached, no button clicked.") end end) end
Parameters: themeName: string — Returns: nothing
Dynamically loads and applies a theme by its name, automatically reloading the interpreter UI with the theme's stylesheet path.
HPR.applyTheme("Dracula")
Parameters: message: string (optional) —
Returns: nothing (terminates process)
Abruptly terminates the HPR process with exit code 1 using the C system call _exit(1) (rather than standard exit(1)). While we cannot envision why you would deliberately want to force-crash the application like this, the utility is provided for extreme debugging circumstances.
If an optional message argument is supplied, HPR will log that message to both standard output (stdout) and standard error (stderr) before terminating. If no argument is provided, HPR will log a generic notice stating that the application has been intentionally crashed by an extension.
HPR.crash("Intended debug crash triggered by extension.")
Environment, OS & Extension Control
Exposes environment, operating system, and loaded extension properties, alongside utilities to dynamically load, unload, and refresh extensions.
Returns: string (e.g. "Linux" or "Windows")
Returns the operating system name HPR is currently running on.
local os = HPR.getOsName() print("Current OS: " .. os)
Returns: string (e.g. "Hyprland", "Kde", or "")
Returns the specific desktop compositor or environment HPR is actively using to poll windows if running on Linux. On Windows systems, this function returns an empty string "".
local comp = HPR.getEnvironmentName() print("Compositor: " .. comp)
Returns: table (array of loaded extension tables)
Returns an array of all loaded extensions in HPR. Each element in the returned table is a key-value dictionary representing an active extension, containing its authorName and extensionName properties. Useful for discovery, verification, and inter-extension dependencies.
local plugins = HPR.getLoadedExtensions() for i, ext in ipairs(plugins) do print("Loaded extension: " .. ext.extensionName .. " by " .. ext.authorName) end
Returns: boolean
Returns true if the current extension is determined to be compatible with the running version of HPR (i.e. the current HPR version is present in the extension's top-level HPR.versionSupport table), or false if it is incompatible or missing version support.
if not HPR.amICompatible() then print("Warning: Running extension on an untested HPR version!") end
Parameters: authorName: string, extensionName: string — Returns: nothing
Unloads an active extension by matching its author and extension names.
HPR.unloadExtension("Plexescor", "My Custom HPR Extension")
Parameters: authorName: string, extensionName: string — Returns: nothing
Reloads an active extension by matching its author and extension names, triggering its initialization sequence again.
HPR.reloadExtension("Plexescor", "My Custom HPR Extension")
Parameters: None — Returns: nothing
Asynchronously triggers a full reload of the calling extension after a short 50ms delay, allowing an extension to safely re-initialize itself without hanging its own execution thread.
HPR.reloadMyself()
Parameters: None — Returns: nothing
Scans the extensions directory for new, updated, or removed extensions, dynamically syncing the application's extension state.
HPR.refreshExtensions()
Parameters: None — Returns: string (relative path)
Returns the relative directory path of the running extension relative to HPR's root extension directory (ending with a trailing slash). Returns an empty string "" if the extension file resides directly in the root extensions folder.
local relDir = HPR.getExtensionDir() print("Relative extension dir: " .. relDir)
Parameters: None — Returns: string (full absolute directory path)
Returns the full canonical absolute filesystem directory path of the running extension (ending with a trailing / or \). Recommended when constructing absolute paths for loading native shared libraries via package.loadlib() or accessing external files.
local absDir = HPR.getExtensionAbsoluteDir() print("Absolute extension dir: " .. absDir)
Parameters: bool: boolean — Returns: nothing
Dynamically configures whether API functions must be prefixed with HPR. at runtime. If toggled to false, API functions are instantly exposed globally.
HPR.setUseHPRTablePrefix(false)
Parameters: bool: boolean — Returns: nothing
Dynamically configures whether HPR API functions require the legacy _E suffix (e.g. writeCsv_E) at runtime.
HPR.setLegacyAPISuffix(true)
Parameters: versions: table (array of version strings) — Returns: nothing
Dynamically configures the list of supported HPR application versions at runtime, instantly re-triggering the application's compatibility check logic.
HPR.setVersionSupport({"0.9.8", "0.9.7"})
Window State Information
Returns:
string (the raw window class name)
Reads directly from HPR's active tracking backend state using a thread-safe C++ lambda. It returns the raw window class name of the currently focused window (before applying aliases or filters). Useful for checking active status or inspecting the currently running window.
local windowClass = HPR.getCurrentWindow() print("Raw active window class: " .. windowClass) -- e.g. "firefox" or "kitty"
Returns:
string (the raw window title)
Reads and returns the raw window title string of the currently focused window. This is critical for extensions tracking browser websites, active files in editors, or terminal commands.
local title = HPR.getCurrentTitle() print("Raw active window title: " .. title) -- e.g. "Google — Mozilla Firefox"
Returns: nothing
Instructs HPR's CurrentWindowManager to pause all active window-tracking polling. Useful when
an extension needs to temporarily suppress tracking — for example, during a screen-lock event or a
privacy-sensitive period. Pair with HPR.startTracking() to resume.
HPR.stopTracking()
-- ... do sensitive work ...
HPR.startTracking()
Returns: nothing
Resumes HPR's CurrentWindowManager window-tracking polling after it has been paused with
HPR.stopTracking(). Has no effect if tracking is already running.
HPR.startTracking()
Parameters: command: string — Returns:
string (stdout)
Executes a shell command synchronously and returns the standard output (stdout) as a string. Any trailing whitespace is preserved. The function blocks until completion; keep commands fast.
To prevent accidental or malicious destruction from extensions, HPR actively filters and blocks dangerous commands. If you plan/planned to use dangerous commands, FUCK OFF 🖕🖕.
Some examples of blocked commands:
rm, rmdir, chmod, sudo, su,
mkfs, fdisk, shutdown, reboot, curl,
wget, python, bash, del, reg,
apt, pip, systemctl, and common shell injection patterns like
| sh or $(rm).
local output = HPR.runSystemCommand("uptime") print(output)
Parameters: title: string, message: string — Returns:
nothing
Displays a native OS desktop notification. This function is fully cross-platform: on Linux it uses native D-Bus notifications over libdbus-1 directly, and on Windows it leverages the native Windows Action Center Toast Notification API via WinToast. It automatically handles security sanitization and parameter wrapping across different operating systems.
HPR.showNotification("Work Completed!", "Your task has finished running.")
Alias Management
HPR provides high-performance lookup in Lua matching its internal configurations for mapping raw window names to user-friendly pretty-names.
Parameters: command: string (raw name) — Returns:
string (aliased pretty name)
Returns the user's defined alias pretty-name for a given application class (e.g. mapping
kitty to Terminal).
Parameters: command: string — Returns:
string (aliased pretty name)
Returns the user's configured alias for browser tab patterns (website pretty-names).
Parameters: command: string — Returns:
string (aliased pretty name)
Returns the user's configured pretty alias for active development projects.
Parameters: aliasName: string — Returns:
string (original raw name)
Looks up the reverse cache of application aliases to resolve a pretty name (e.g. Terminal)
back to its original raw identifier (e.g. kitty).
Parameters: aliasName: string — Returns:
string (original raw tab)
Resolves a pretty tab website alias back to the original raw web title representation.
Parameters: aliasName: string — Returns:
string (original raw project name)
Resolves a pretty project name back to the original raw project workspace directory or file path keyword.
Process & Limits Management
Enables extensions to query active process IDs (PIDs), terminate specific running applications, and dynamically set or get daily usage limits and goals.
To ensure full consistency, always use the class name returned by HPR.getCurrentWindow() as the input name parameter for functions like getPid, setLimit, getLimit, setGoal, and getGoal. HPR checks and maps limits/goals against the raw window class names, so query inputs must match the exact string returned by HPR's active tracking loop.
Parameters: rawName: string — Returns:
string (PID string or empty string)
Looks up and returns the process ID (PID) associated with the currently tracked active window of the given application class name. Returns an empty string if no window is tracked or if the current active backend does not provide PID information.
local pid = HPR.getPid("firefox") if pid ~= "" then print("Firefox PID is: " .. pid) end
Parameters: pid: string — Returns:
nothing
Forcefully terminates the process associated with the given process ID (PID). On Linux systems, this issues a kill -9 <pid> signal, and on Windows it executes taskkill /F /PID <pid>.
This operation is aggressive and bypasses standard application exit prompts. Any unsaved data in the target application will be lost. Use with care.
local pid = HPR.getPid("steam") if pid ~= "" then HPR.killPid(pid) end
Parameters: name: string (app name/alias), minutes: int (daily limit in minutes) — Returns:
nothing
Sets a daily usage limit (in minutes) for the specified application. HPR's LimitsManager periodically checks active tracking durations against this limit. When reached, warning/reached alerts and termination protocols are handled by the system.
HPR.setLimit("firefox", 60) -- Limit Firefox to 1 hour daily
Parameters: name: string — Returns:
int (limit in minutes, or -1)
Retrieves the active daily usage limit in minutes for the given application class name. Returns -1 if no limit has been configured.
local mins = HPR.getLimit("firefox") if mins ~= -1 then print("Firefox limit: " .. mins .. " minutes") end
Parameters: name: string (app name/alias), minutes: int (daily goal in minutes) — Returns:
nothing
Sets a daily usage/productivity goal (in minutes) for the specified application. Useful for rewarding focus time or tracking daily routines in developer environments and text editors.
HPR.setGoal("code", 120) -- Set a daily coding goal of 2 hours
Parameters: name: string — Returns:
int (goal in minutes, or -1)
Retrieves the active daily productivity goal in minutes for the given application. Returns -1 if no goal has been configured.
local mins = HPR.getGoal("code") if mins ~= -1 then print("Daily coding goal: " .. mins .. " minutes") end
Backend Registration
Parameters:
(name, matchesEnvironment, initialize, getCurrentWindow, getCurrentTitle, getCurrentPid)
Registers a completely new custom tracking backend dynamically. HPR's backend manager calls the registered functions during startup to inspect compositors and get current window classes, titles, and process IDs. For a detailed guide on using this, please consult the Custom Backend Tutorial.
function init() HPR.registerBackend( "MyCompositor", function(env) return env:find("MyCompositor") ~= nil end, function() print("Loaded!") end, function() return "browser" end, function() return "Home Page" end, function() return "1234" end ) end
Preferring Extension Backends: If you register a custom window tracking backend for a compositor or desktop environment that HPR already natively supports (like Hyprland, GNOME, KDE, Cinnamon, niri, or Windows), HPR will prioritize and use your extension's implementation instead of its native C++ one.
HPR checks registered backends in reverse order of registration. Because Lua extensions load after the native backends are registered, your custom extension backend is positioned last in the registry and will be evaluated first during startup checks.
Warning: Dynamically registered custom Lua backends are considered experimental. You may experience bugs, instability, or memory leaks depending on how system commands are polled and the stability of your compositor integrations. Native C++ backends should be preferred when available.
Event Hub & Messaging
The Event Hub is HPR's high-performance messaging core, acting as a real-time bridge to dispatch lifecycle signals, system changes, or dynamic messages between C++ and Lua extensions.
Parameters: eventName: string, callback: function —
Returns: int (subscription ID)
Subscribes a Lua callback function to a system or custom named event. Whenever the event is emitted, your callback will run. Returns a unique subscription ID integer that must be saved to disconnect later.
Thread Safety: Events are evaluated synchronously and are completely guarded by a std::recursive_mutex lock to ensure that cross-extension or multi-threaded executions do not collide inside the Lua state.
Automatic Lifecycle Cleanup: HPR's C++ manager dynamically tracks all registered connections. When your extension is shut down or stopped, all registered subscriptions are automatically disconnected and safely removed from the core C++ registry, completely preventing memory leaks, dangling pointer references, and shutdown segfaults. Manual disconnection is no longer mandatory for memory safety.
LOAD_DATABASE_SINGULAR, HISTORY_LOADED_SINGULAR, LOAD_LIVE_DATA,
APPRROR, MIDNIGHT_ROLLOVER, WINDOW_CHANGED, UI_READY, MISC_KEY_PRESSED, MISC_KEY_RELEASEDNote on
LOAD_DATABASE_SINGULAR: Emitted when the user starts loading a past day's SQLite database file.Note on
HISTORY_LOADED_SINGULAR: Emitted when the query or load of a past day's historical database has successfully completed and all historical stats are ready.Note on
LOAD_LIVE_DATA: Emitted when the user switches view from historical databases back to the real-time active daily logs.Note on
WINDOW_CHANGED: Passes a Lua table payload containing data.fromWindow (previous window class name string) and data.toWindow (focused window class name string).Note on
UI_READY: Emitted once when the Slint UI is 100% initialized and running. Extensions can subscribe to this signal to safely begin UI operations or initialize background window polling.Note on
MISC_KEY_PRESSED: Emitted when the user presses a keyboard key while focusing the Miscellaneous Image Panel. Passes the string representation of the pressed key directly (e.g. "a", "Enter").Note on
MISC_KEY_RELEASED: Emitted when the user releases a keyboard key while focusing the Miscellaneous Image Panel. Passes the string representation of the released key directly.Any other string is treated as a dynamic custom event shared between extensions.
local subId = HPR.connect("WINDOW_CHANGED", function(data) print("Active window class changed to: " .. data.toWindow) end)
Parameters: eventName: string, data: table (optional) —
Returns: nothing
Broadcasts a named signal to the Event Hub, triggering all active callbacks connected to this event name. You can optionally pass a Lua table/value containing a custom payload structure.
HPR.emit("my_custom_alarm", { temp = 82.5, warning = true })
Parameters: eventName: string, subscriptionId: int —
Returns: nothing
Unsubscribes a specific listener using its unique subscription ID. Calling this manually inside
onExit() is supported for precise runtime lifecycle management, but is no longer required for basic memory safety.
function onExit() HPR.disconnect("MIDNIGHT_ROLLOVER", subId) end
Network & JSON Utilities
HPR provides high-performance utility helpers to query REST APIs and serialize or parse JSON payloads without needing slow external scripting dependencies.
Parameters: port: int, handler: function —
Returns: boolean (success)
Starts an embedded HTTP server on the given port, delegating all incoming requests to the provided Lua
handler function. Returns true if the server bound successfully, false
otherwise. The handler is called on the extension's background thread for each incoming request. Useful
for exposing a local REST API or webhook receiver from within your extension.
Handler Signature: The handler function receives a single Lua table representing the incoming HTTP request, containing the following fields:
method(string): The HTTP request method (e.g."GET","POST").path(string): The requested path URL string (e.g."/api/webhook").body(string): The raw request body text.headers(table): A Lua dictionary mapping request header keys to header value strings.
Handler Return Format: The handler can return:
- A **table** containing:
status(integer, optional): The HTTP response status code (defaults to200).body(string, optional): The response body string.headers(table, optional): Dictionary of response header name-value pairs.
- A **string**: Treated directly as the response body with a
200 OKstatus. - **Nothing** (or nil): Responds with an empty body and
200 OK.
function init() local ok = HPR.startServer(8080, function(request) print("Incoming request method: " .. tostring(request.method)) print("Incoming request path: " .. tostring(request.path)) print("Incoming request body: " .. tostring(request.body)) -- Return custom response structure return { status = 200, body = "{\"status\":\"success\"}", headers = { ["Content-Type"] = "application/json" } } end) print("Server started: " .. tostring(ok)) end
Parameters: host: string, path: string,
secure: boolean (optional, default true), headers: table (optional) — Returns:
(string, int) (body, status)
Performs a synchronous HTTP or HTTPS GET request using HPR's native, multithreaded network engine. Since each extension is run inside its own background operating system thread, GET requests block only the calling extension's thread, keeping HPR completely responsive.
Loopback DNS Caveat (localhost vs. 127.0.0.1): On modern operating systems, passing
"localhost" triggers the system's standard name resolution (getaddrinfo),
which prioritizes the IPv6 loopback address [::1] over the IPv4 address
127.0.0.1. If a local server binds strictly to the IPv4 address 127.0.0.1, any
request targeting localhost will fail. Always use literal IPv4 addresses like
"127.0.0.1" instead of "localhost" for local communication.
local body, status = HPR.httpGet("127.0.0.1:5600", "/api/0/buckets/", false) -- Example with custom headers: local customHeaders = { ["X-HPR-Test"] = "hello123" } local body, status = HPR.httpGet("httpbin.org", "/headers", true, customHeaders)
Parameters: host: string, path: string,
body: string, secure: boolean (optional, default true), headers: table (optional) —
Returns: (string, int) (responseBody, status)
Performs a synchronous HTTP or HTTPS POST request. Very useful for pushing tracked events out to local controllers or dispatching Discord webhooks.
Loopback DNS Caveat (localhost vs. 127.0.0.1): Just like GET requests, local POST
targets should use the literal IPv4 "127.0.0.1" instead of "localhost". See
httpGet for the full explanation.
local res, status = HPR.httpPost("api.webhook.com", "/send", jsonString) -- Example with Bearer Auth and Content-Type: local headers = { ["Authorization"] = "Bearer testtoken123", ["Content-Type"] = "application/json" } local res, status = HPR.httpPost("httpbin.org", "/post", jsonString, true, headers)
Parameters: host: string, path: string,
body: string, secure: boolean (optional, default true), headers: table (optional) —
Returns: (string, int) (responseBody, status)
Performs a synchronous HTTP or HTTPS PUT request using HPR's native network engine.
Loopback DNS Caveat (localhost vs. 127.0.0.1): Just like GET and POST requests, local PUT
targets should use the literal IPv4 "127.0.0.1" instead of "localhost". See
httpGet for the full explanation.
local res, status = HPR.httpPut("api.webhook.com", "/update", jsonString) -- Example with custom headers: local headers = { ["Content-Type"] = "application/json" } local res, status = HPR.httpPut("httpbin.org", "/put", jsonString, true, headers)
Parameters: host: string, path: string,
secure: boolean (optional, default true), headers: table (optional) —
Returns: (string, int) (responseBody, status)
Performs a synchronous HTTP or HTTPS DELETE request using HPR's native network engine. Useful for removing remote resources, cleaning up Firebase nodes, or any REST API that uses the DELETE verb.
Loopback DNS Caveat (localhost vs. 127.0.0.1): Just like GET, POST and PUT requests, local DELETE
targets should use the literal IPv4 "127.0.0.1" instead of "localhost". See
httpGet for the full explanation.
local res, status = HPR.httpDelete("api.example.com", "/resource/123") -- With custom headers: local headers = { ["Authorization"] = "Bearer mytoken" } local res, status = HPR.httpDelete("api.example.com", "/resource/123", true, headers)
Parameters: str: string (ISO 8601 datetime) —
Returns: uint64 (Unix timestamp in milliseconds, or 0 on parse
failure)
Parses an ISO 8601 datetime string (e.g. "2024-05-21T14:30:00.500Z") and returns the
corresponding Unix timestamp in milliseconds. Subsecond precision up to milliseconds is preserved. Returns
0 if the string cannot be parsed. Commonly used when consuming timestamps from external REST
APIs (such as Spotify or calendar services) that return ISO 8601 formatted dates.
local ms = HPR.parseISO8601("2024-05-21T14:30:00.500Z") print("Timestamp ms: " .. ms)
Parameters: jsonStr: string, fieldPath: string (optional)
— Returns: table or any
A fast convenience transmuter that instantly translates a raw JSON string into a Lua table, sparing you from bundling slow manual string parsers. An optional dot-separated key path can be passed to pluck nested elements directly.
local data = HPR.parseJSON(rawJson) local nestedValue = HPR.parseJSON(rawJson, "data.url")
Parameters: luaTable: table — Returns:
string (JSON representation)
A convenience utility that serializes a standard Lua table into a minified JSON string representation, ready for outgoing POST requests.
local jsonStr = HPR.toJSON({ name = "firefox", active = true })
Database Operations
HPR gives every extension direct access to its SQLite database. All extensions share the same database file, so choose unique table names to avoid conflicts.
Parameters: sql: string, params: table (optional) —
Returns: nothing
Executes a write SQL statement (CREATE, INSERT, UPDATE, DELETE) against HPR's SQLite database. The
optional second argument is a Lua table of strings bound to ? placeholders in the SQL query,
preventing SQL injection. Always use parameterized queries when inserting user-derived data.
-- Create a table HPR.dbExecute([[ create table if not exists my_data ( key text unique, value text ); ]]) -- Insert with parameterized values HPR.dbExecute( "insert or replace into my_data (key, value) values (?, ?);", { "active_app", "firefox" } )
Parameters: sql: string, params: table (optional) —
Returns: table (array of row tables)
Executes a read SQL statement (SELECT) and returns the results as a Lua table. Each element in the returned table is a row, represented as a Lua table with column names as keys.
local rows = HPR.dbQuery("select key, value from my_data;") for i, row in ipairs(rows) do print(row.key .. " = " .. row.value) end
Parameters: none — Returns: string (full absolute
file path)
Returns the absolute, resolved filesystem path of the currently loaded historical database. Returns an empty string if HPR is currently in live-tracking mode.
local histPath = HPR.getLoadedHistDbPath() print("Loaded past DB path: " .. histPath)
Parameters: sql: string, params: table (optional) —
Returns: table (array of rows)
Executes a read SQL query against the currently active historical database file (set during a
"LOAD_DATABASE_SINGULAR" calendar switch). This is a safe, high-level helper that completely
abstracts database opening, folder traversal, and closing operations. Blocks for up to 5 seconds waiting
for the async historical DB load to complete before running the query.
local rows = HPR.dbQueryHistorical("select song_name from spotify_songs;")
Parameters: date: string (e.g. "30-05-26") —
Returns: string (full absolute file path)
Resolves a given date string into its corresponding absolute database file path on the system. Excellent for locating specific days to query in an isolated context.
Tip: Dynamic Date Construction
If you want to construct the date string dynamically for today, you can call HPR.getTime_MS() to get the current Unix timestamp in milliseconds and pass it to HPR.convertToDate_DDMMYY(ms). This yields the exact DD-MM-YY format expected by this function.
Note on File Extension:
This function automatically appends the .db file extension to the resolved absolute path, so you do not need to add it manually inside your input string.
-- Dynamically resolve today's path local today_ms = HPR.getTime_MS() local today_str = HPR.convertToDate_DDMMYY(today_ms) local dbPath = HPR.getDbPathForDate(today_str) print(dbPath)
Parameters: dbPath: string, sql: string, params: table (optional) —
Returns: table (array of row tables)
Executes a SELECT query directly against a specific database file path in a completely isolated context. It returns the query results directly as a native Lua table without affecting HPR's main application state or active GUI charts.
local path = HPR.getDbPathForDate("28-05-26") local rows = HPR.dbQueryPath(path, "select name, duration from app_usage;")
Parameters: days: int (number of past days to query), mode: string ("total", "average", or "raw"), sql: string, params: table (optional) —
Returns: table (array of row tables)
Queries multiple daily database files across the last days days starting from today.
Under the Hood (Parallel Execution): To keep performance extremely fast and optimized, this function spawns a pool of parallel background worker threads (one thread per daily database file). Each thread opens its day's database, executes the SQL query concurrently, and writes the results to a thread-safe memory index before joining back.
The results are aggregated and merged synchronously before returning to Lua:
- If
modeis"total", values in the"duration"column are summed up across all days (grouped by the other returned columns). - If
modeis"average", values in the"duration"column are averaged by dividing the sum by the count of days that specific grouped record was actively tracked. - If
modeis anything else (e.g."raw"), no daily aggregation is performed; instead, a flat list of rows is returned exactly as each day's query produced them, with no extra columns injected.
-- Get total duration of app usage over the last 7 days local rows = HPR.dbQueryNumber( 7, "total", "select name, duration from app_usage;" )
Parameters: dateFrom: string ("DD-MM-YY"), dateTo: string ("DD-MM-YY"), mode: string ("total", "average", or "raw"), sql: string, params: table (optional) —
Returns: table (array of row tables)
Queries multiple daily database files within a specific date range. Like the number query, it executes queries concurrently using a pool of parallel worker threads (one thread per day).
Date Order-Reversal: Under the hood, this function automatically detects and handles reversed date inputs. If the start date is chronologically after the end date, HPR swaps them before execution so the query succeeds seamlessly.
Integrates the same mode aggregation logic ("total", "average", or "raw") and blocks the calling Lua thread for up to 7500ms while this query's parallel workers complete, returning an empty table if they don't finish in time.
local rows = HPR.dbQueryRange( "01-06-26", "07-06-26", "average", "select name, duration from app_usage;" )
UI Integration
Extensions can push data directly into HPR's Slint UI and register callbacks for UI events. Values are converted from Lua types to Slint types automatically — strings, numbers, booleans, arrays of structs are all supported. For a step-by-step guide on building a UI-connected extension, see the Custom App Extension Tutorial.
Parameters: name: string, value: any —
Returns: nothing
Sets a named property on the root Slint UI component. The property name must exactly match an
in property defined in app-window.slint. HPR converts the Lua value to a C++
intermediate on the extension thread, then dispatches the Slint update to the main UI thread. Supported
types: string, number, boolean, table (array → Slint
model, key-value → Slint struct). If the UI is not yet loaded, the call is silently ignored.
-- Set a simple string property HPR.setUiProperty("windowName_S", "firefox") -- Set an array of structs (each struct maps to a Slint struct) HPR.setUiProperty("spotifySongs_S", { { name = "Song A", artist = "Artist 1", duration = "01:23:45", duration_i = 5025000 }, { name = "Song B", artist = "Artist 2", duration = "00:45:30", duration_i = 2730000 }, })
Parameters: name: string, luaFunction: function(...any) —
Returns: nothing (registers the handler)
Registers a Lua function as a handler for a Slint callback. When the named callback is triggered from the
Slint UI (e.g. clicking a row or a button), HPR invokes your Lua function.
Argument Forwarding: As implemented in the C++ extension manager, the callback lambda intercepts the arguments passed from the Slint UI (auto args), converts each from its Slint type to C++ and then to Lua objects, and forwards all of them as positional parameters directly to your Lua function.
Return Behavior: The C++ handler is defined with a void/empty return (return slint::interpreter::Value();). Any values returned by your Lua callback function are ignored, meaning the callback bridge is strictly one-way (parameter-only forwarding from Slint to Lua).
The callback name must exactly match a callback defined in the Slint component. If the UI is not yet loaded, the call is silently ignored.
HPR.registerUiCallback("songRowClicked", function(songData) print("User clicked song: " .. tostring(songData.name) .. " by " .. tostring(songData.artist)) end)
Time & Date Utilities
HPR exposes its internal time formatting and parsing functions to Lua. All time values are in milliseconds unless noted otherwise.
Parameters: ms: int (milliseconds) — Returns:
string
Converts a duration in milliseconds to a human-readable "HH:MM:SS" formatted string. Commonly
used for displaying tracked time in the UI.
local formatted = HPR.formatTime_HHMMSS(5025000) print(formatted) -- "01:23:45"
Parameters: ms: int (Unix timestamp in ms) — Returns:
string
Converts a Unix timestamp in milliseconds to a date string in "DD-MM-YY" format.
Parameters: ms: int (Unix timestamp in ms) — Returns:
string
Converts a Unix timestamp in milliseconds to a date string in "MM-YY" format (month and year
only).
Parameters: ms: int (Unix timestamp in ms) — Returns:
string
Converts a Unix timestamp in milliseconds to a 12-hour time string in "HH:MM:SS AM/PM"
format.
Parameters: dateStr: string ("DD-MM-YY") —
Returns: int (Unix timestamp in ms)
Parses a "DD-MM-YY" date string and returns the corresponding Unix timestamp in milliseconds.
The inverse of convertToDate_DDMMYY.
Parameters: dateStr: string ("MM-YY") —
Returns: int (Unix timestamp in ms)
Parses a "MM-YY" date string and returns the corresponding Unix timestamp in milliseconds.
The inverse of convertToDate_MMYY.
Parameters: dateStr: string ("DD-MM-YY") —
Returns: string ("MM-YY")
Extracts the month-year portion from a full "DD-MM-YY" date string, returning just
"MM-YY". Useful for grouping daily records by month.
Dynamic Library Loading (Dynamic Libraries)
HPR supports dynamic library loading to give extensions unlimited execution capabilities.
By enabling the "Allow Extensions to Load Native Libraries" toggle in Settings, Lua scripts gain access to the standard package library, allowing loading of compiled C/C++ libraries (.so / .dll) via package.loadlib() or require().
This enables developers to run low-level OS operations, custom tray menus, high-performance algorithms, or anything they want.
To build a compatible module, compile it with the extern "C" entrypoint. This entrypoint is executed when the library is loaded and returns the module table or registers global functions.
// mylib.cpp (Vanilla C++) #include <lua.hpp> #include <iostream> // The native function called from Lua static int l_hello(lua_State* L) { std::cout << "Yo i am native" << std::endl; return 0; } // Entry point called by Lua's package loader extern "C" int luaopen_mylib(lua_State* L) { lua_register(L, "hello", l_hello); // Registers function globally return 0; }
Compile the library:
g++ -shared -fPIC -o mylib.so mylib.cpp
Then, load and run it inside your extension:
-- main.lua local extDir = HPR.getExtensionAbsoluteDir() local dllPath = (HPR.getOsName() == "Windows") and (extDir .. "mylib.dll") or (extDir .. "mylib.so") local loader, err = package.loadlib(dllPath, "luaopen_mylib") if loader then loader() -- Runs luaopen_mylib to register global function hello() -- Prints: Yo i am native else HPR.log("MyExtension", "Failed to load native library: " .. tostring(err)) end
C Signature: extern "C" HPR_API void HPR_invokeOnSlintThread(void(*fn)(void*), void* userdata);
Why use it: Slint UI components, windowing, and property bindings are thread-bound and must strictly execute on Slint's main event loop thread. When writing compiled C/C++ extensions (`.so` on Linux / `.dll` on Windows) loaded via Lua, attempting to construct UI instances or mutate Slint values from background worker threads causes thread assertions or application crashes. HPR_invokeOnSlintThread safely dispatches arbitrary C/C++ callbacks to run directly on the Slint main thread loop.
How to use it: Dynamically resolve HPR_invokeOnSlintThread using GetProcAddress (Windows) or dlsym (Linux). Pass a static callback function pointer void(*)(void*) and payload context pointer void*. Use std::promise and std::future if the calling thread needs to block until main-thread execution finishes.
The code snippet below is adapted from the official HPR-Store repository (src/library.cpp):
// Example from HPR-Store: https://github.com/plexescor/HPR-Store typedef void (*SlintInvokeFn)(void (*)(void *), void *); static SlintInvokeFn g_hprInvoke = nullptr; extern "C" HPR_EXPORT void initialize(lua_State *L) { // Dynamically resolve the export symbol from HPR host executable #ifdef _WIN32 HMODULE hpr = GetModuleHandleA(nullptr); g_hprInvoke = (SlintInvokeFn)GetProcAddress(hpr, "HPR_invokeOnSlintThread"); #else g_hprInvoke = (SlintInvokeFn)dlsym(RTLD_DEFAULT, "HPR_invokeOnSlintThread"); #endif if (!g_hprInvoke) return; struct InitData { std::promise<void> *ready; }; auto data = new InitData{ new std::promise<void>() }; auto fut = data->ready->get_future(); // Safely dispatch UI creation to Slint event loop thread g_hprInvoke([](void *ud) { auto d = static_cast<InitData *>(ud); gui = std::make_unique<GUI>(); d->ready->set_value(); delete d->ready; delete d; }, data); fut.wait(); // Block caller until main-thread Slint GUI init completes }