iToverDose/Software· 1 JUNE 2026 · 12:04

How Lua extensions power a lightweight C++ activity tracker

Adding a Lua scripting layer to a C++ activity tracker unlocks custom automation and deep integration without compromising performance or stability. Here’s how one developer engineered a safe, isolated extension system—and the pitfalls they encountered.

DEV Community4 min read0 Comments

A growing number of productivity tools now ship with scripting support, but few do it as cleanly as HPR, a lightweight C++ activity tracker that tracks active windows, builds usage timelines, and runs entirely offline. What started as a simple tracker evolved into a platform for user-driven features when its creator decided to embed a Lua engine inside it—enabling anyone to extend its behavior without touching the core codebase.

HPR’s extension system is designed to be safe, fast, and invisible to the end user. Lua scripts placed in a dedicated folder are scanned automatically, loaded into isolated sol::state environments, and executed in their own threads. Each extension operates in complete isolation, so a misbehaving script can’t crash the tracker or freeze the UI. The extension lifecycle is deliberately minimal: developers only need to implement three functions—init, onTick, and onExit—to get started.

A minimal extension lifecycle

The extension system follows a straightforward flow. When HPR starts, it scans the extensions directory recursively and loads every .lua file it finds. Each script runs in its own Lua state, with a dedicated thread handling execution. The init function returns the desired tick interval in milliseconds, which determines how often onTick is called. The onExit function runs during shutdown for cleanup.

function init()
  return 500 -- Tick every 500ms
end

function onTick(delta)
  -- Called every 500ms with the time since last call
end

function onExit()
  -- Cleanup logic here
end

This simplicity isn’t just developer-friendly—it also makes the system robust. A slow or faulty extension can’t block the main tracking loop, and crashes are contained within the script’s own environment.

Extending with a rich API surface

While the lifecycle is simple, the exposed API is anything but limited. Lua scripts can tap into HPR’s core features through a set of carefully restricted entry points:

  • Query the current active window using HPR.getCurrentWindow_E().
  • Directly read and write to the embedded SQLite database with HPR.dbQuery_E() and HPR.dbExecute_E().
  • Subscribe to internal events such as window changes, midnight rollovers, or history loads.
  • Register custom window detection backends for unsupported compositors.
  • Modify the Slint-based UI and register callbacks from Lua.
  • Make HTTP requests, host a server, parse JSON, or handle CSV files.
  • Control the tracking loop itself, including pausing and resuming activity tracking.

The ability to pause tracking was particularly useful for building AFK detection. One extension uses this feature to stop the main tracker when the user is idle and resume it when they return, all while maintaining data consistency.

Engineering safety in a multithreaded world

The real challenge wasn’t writing the Lua integration—it was threading it safely across a complex runtime. HPR’s architecture includes multiple threads: the Lua extension thread, the Slint UI event loop, the window poller, and the database writer. Each thread needs access to shared state, but none of them can safely interact with each other without coordination.

To prevent race conditions, HPR uses a recursive_mutex for every Lua extension. All calls into the Lua state are wrapped in this mutex, ensuring thread-safe access. For UI updates, the system relies on slint::invoke_from_event_loop, which queues UI operations on the correct thread. Callbacks registered from Lua and triggered by Slint acquire the Lua mutex before re-entering the script, avoiding deadlocks during bidirectional communication.

Another subtle but critical issue was cyclic table detection. If a Lua script pushes a self-referential table into a UI property, the system would previously recurse infinitely and crash. The fix introduced a visited set that tracks table pointers during conversion. If a cycle is detected, the system returns an error instead of crashing—turning a potential segfault into a graceful failure.

Putting it to work: simulating ActivityWatch

One practical use case of this system is the AW Parasite extension. The creator wanted to reuse existing ActivityWatch clients like aw-watcher-web and aw-watcher-afk without modifying them. The solution was to spin up an HTTP server on port 5600—the default ActivityWatch port—and mimic its API.

The extension processes heartbeats, tracks URL visits and AFK status, stores everything in HPR’s SQLite database, and updates the UI. When the user becomes inactive, the extension calls HPR.stopTracking_E() to pause the main tracker, then resumes it when activity resumes. The entire logic fits in about 200 lines of Lua and works seamlessly on both Wayland and Windows because it relies only on standard HTTP.

Hot reloading without downtime

HPR supports live extension management. Users can load, unload, or reload individual scripts from the UI without restarting the tracker. A rescan button detects new .lua files dropped into the extensions folder after launch. When unloading, the system sets running = false on the extension’s thread and gives it 450ms to exit cleanly. If the thread doesn’t respond, it’s detached and a warning is logged. Native extensions (.dll or .so) are also supported for cases where Lua’s performance or capabilities fall short, though these run outside the sandbox.

This blend of flexibility, safety, and performance shows how far a carefully engineered scripting layer can take a seemingly simple tool. For developers tired of monolithic apps or cloud-dependent trackers, HPR offers a compelling model: a lean, offline-first foundation with room for endless customization through Lua.

HPR is free, open source software. If this approach resonates with your workflow, explore the project on the official repository.

AI summary

C++23 ile geliştirilen açık kaynaklı HPR aktivite izleyicisine nasıl Lua tabanlı bir uzantı motoru eklendi? Ölümcül deadlocklardan kaçınma ve API kullanımı hakkında detaylar.

Comments

00
LEAVE A COMMENT
ID #OR4GBV

0 / 1200 CHARACTERS

Human check

9 + 7 = ?

Will appear after editor review

Moderation · Spam protection active

No approved comments yet. Be first.