Omahub
← All plugins
K

Omascreentime

by Kennet Postigo

Per-app screen time in the Omarchy bar. Click the chip, or: omarchy-shell shell toggle postman.omascreentime

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
f50be30
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
f50be30
Reviewed
1 month ago

Omascreentime is a screen-time tracker that runs a Rust daemon to monitor focused windows via Hyprland events and /proc, writing history to the user's state directory. The code is transparent, well-documented, and contains no destructive commands, network exfiltration, or obfuscation. The build and install scripts are standard and only affect the user's own configuration.

  • The daemon reads /proc and Hyprland events, which is expected for a screen-time tracker but does collect per-app usage data stored locally.
  • The optional install-menu.sh script modifies the user's menu extension file, but it is opt-in and only adds a menu entry.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/kennetpostigo/omascreentime --enable
Productivity #Hyprland #bar #quickshell

Omascreentime

See where the day went without leaving the Omarchy bar.

Omascreentime is a screen-time explorer for Omarchy. A clock-glyph chip on the bar shows today's focused time. Click it and a panel peeks out — the same chrome as Omadisk, Network, and Display — with three tabs:

  • Today — donut + every app you focused for at least 5 seconds, and a 14-day sparkline when you click a row
  • Week — last 7 days, weekends called out, goal hit/miss
  • Goals — a daily cap and per-app goals, with streaks

Only the focused window counts. Idle, lock, and the screensaver do not. When a terminal is focused, the tracker looks inside it, so Neovim is Neovim, not Ghostty.

Plugin id: postman.omascreentime · License: MIT · Kinds: service + bar widget

The UI never watches windows itself. A small Rust tracker (omascreentime-track) listens to Hyprland, persists history, and speaks a capped NDJSON view so the single omarchy-shell process stays responsive.

Screenshots

Today — donut, full app list, click a row for a 14-day trend:

Omascreentime Today tab with a donut, app list, and 14-day trend

Week — last 7 days and whether the daily goal was hit:

Omascreentime Week tab with a 7-day strip and hit/miss rows

Goals — daily total plus per-app targets and streaks:

Omascreentime Goals tab with daily and per-app goal editors

Install

omarchy plugin add clones the plugin. It does not compile the tracker. You need mise (or any Rust toolchain) once, after clone.

omarchy plugin add https://github.com/kennetpostigo/omascreentime.git --enable
cd ~/.config/omarchy/plugins/postman.omascreentime
mise install
./scripts/build.sh
omarchy-shell shell rescanPlugins

If the chip did not land on the right of the bar:

omarchy plugin enable postman.omascreentime --section right

Optional Super-menu entry (opt-in; writes only trigger.omascreentime into your menu extension):

./scripts/install-menu.sh

Use

Click the chip, or:

omarchy-shell shell toggle postman.omascreentime
Key Action
1 / 2 / 3 Today / Week / Goals
arrows Scroll the panel
j / k Move in the app list
click an app Pin it and show a 14-day trend
p Open Goals
r Refresh the view
g / G Scroll to top / bottom
Esc Close
Right-click the chip Icon-only mode

Widget settings:

Key Default Meaning
idleTimeoutSec 60 Extra idle pause; 0 uses session idle only
dailyGoalMin 480 Daily goal in minutes (0 = off). Edit it on Goals, or: omarchy bar set postman.omascreentime dailyGoalMin 360
keepDays 31 History window
iconOnly false Hide the duration on the bar
omarchy bar move postman.omascreentime --section right

How it works

This section is for anyone who wants to change, port, or harden Omascreentime. Coding agents should also read AGENTS.md. The on-the-wire contract is protocol.md.

Two processes, one overlay

omarchy-shell is one long-lived Quickshell process. Walking /proc or polling Hyprland from QML would stall the desktop.

Omascreentime therefore splits in two:

service/Service.qml            keep the tracker daemon alive
bar/BarWidget.qml              KeyboardPanel peek + bar chip
        │
        ▼
overlay/Overlay.qml            tabs, hover, FileView, Process children
        │
        │  Unix socket  +  atomic snapshot.json
        ▼
target/release/omascreentime-track
        daemon | ensure | status | view | proto | stop
        goal | app-goal

The daemon listens to Hyprland's event socket, resolves the focused window (and the foreground process inside a terminal), and atomically writes snapshot.json. The bar chip and the overlay watch that file. Opening the panel also asks view once for a fresh read. There is no 2-second process poll.

What gets counted

Counts Does not count
The focused Hyprland window Windows open in the background
Video players with inhibitingIdle Idle past idleTimeoutSec / session IdleHint
The app inside a terminal Lock screen, hyprlock, screensaver
Any focus of at least 5 seconds (list) Empty desktop / no window

Midnight: the open bucket is credited to the local day it started on, then a new bucket opens.

Terminal unwrap

Hyprland's window class for a terminal is ghostty / foot / … The interesting app is the foreground process on that pty (nvim, btop, grok, …).

resolve.rs walks only the terminal's process tree (not every pid on the machine). It reads /proc/<pid>/task/*/children — Ghostty and some other terminals fork the shell from a non-main thread, so looking at /proc/<pid>/task/<pid>/children alone would miss it and credit the terminal.

Wrappers are peeled once:

  • runtimes (node, python3.13, ruby, …)
  • launchers (npx, mise, uv, sudo, …)
  • helpers (systemd-inhibit, sleep)

Stop at the first real app so jobs under Neovim are not stolen. Re-resolve every 5s while a terminal stays focused.

Browsers fold to one id (brave-origin → brave, zen-bin → zen). Steam's helper processes fold to steam.

Tracker (src/)

Module Job
hypr.rs Command socket (j/activewindow) + event stream. Lock/unlock are first-class events.
resolve.rs Terminal pty → foreground process group, targeted tree walk
session.rs loginctl show-session $XDG_SESSION_ID for IdleHint / LockedHint
tracker.rs Per-app buckets, midnight rollover, live view / status
store.rs Atomic history.json / snapshot.json
goals.rs Daily + per-app goals, current/best streak, week hit/miss
daemon.rs poll loop, control socket, 30s commit, snapshot skip if unchanged
apps.rs Format, canonical names, display names, week keys
paths.rs State dir (0700), flock, unique tmp names
protocol.rs NDJSON constructors
main.rs CLI

Crash loss is bounded by the 30s commit. A signal flushes the open bucket and does not leave a torn JSON file. Snapshots are rename-replaced without fsync; history is fsync'd.

State:

~/.local/state/omascreentime/     # or $XDG_STATE_HOME/omascreentime
  history.json                    # { v, days: { YYYY-MM-DD: { total, apps } } }
  snapshot.json                   # last view event (bar + overlay watch this)
  goals.json                      # dailyMin + per-app minutes
  daemon.lock / daemon.pid

$XDG_RUNTIME_DIR/omascreentime/omascreentime.sock

Overlay data flow

  1. Service starts (and every 60s ensures) the daemon.
  2. Bar chip FileViews snapshot.json. No periodic Process.
  3. Open → startSession: reload snapshot, ensure, one view.
  4. apps is the full list (≥5s). Overlay.relayout groups the donut to 6 slices (tail → Other). The list shows every row — a 39s Tensaku session is Tensaku, not Other.
  5. Shared hover: one hoverId + hoverTick. Donut and list both read it. Do not clear hover on canvas onExited.
  6. Click a row to pin focusId and show TrendChart (14 days).
  7. Header (logo, title, time, tabs) stays outside the Flickable so it stays pinned while the body scrolls.
  8. g / G / arrows scroll. 1 2 3 switch tabs.

Slice layout lives in overlay/OverlayModel.js (layoutSlices, hitTestSlices, groupedApps, accent-derived hues). ListModel must not use a role named color — Quickshell treats that as reserved.

Limits and safety

  • The tracker reads compositor events and /proc. It does not keylog.
  • History lists every focused app id — treat the state dir as private.
  • Plugins run unsandboxed inside omarchy-shell. Review the source before enabling anything.
  • omarchy plugin add never runs install hooks. A clone without ./scripts/build.sh shows a missing-tracker error.

Repository map

manifest.json                 plugin id, service + bar-widget schema
service/Service.qml           supervise omascreentime-track
bar/BarWidget.qml             chip + KeyboardPanel host
bar/Model.js                  chip text / tooltip / parse
overlay/Overlay.qml           session, tabs, FileView, keyboard
overlay/OverlayModel.js       donut layout, hit-test, palette, grouping
overlay/Hero.qml              pinned title + tabs
overlay/DonutCanvas.qml       wedges
overlay/AppList.qml           full app list
overlay/TrendChart.qml        14-day sparkline
overlay/WeekPanel.qml         7-day strip + rows
overlay/GoalsPanel.qml        daily + per-app goals
overlay/AppPicker.qml         searchable add-goal control
overlay/Format.js             durations
src/*.rs                      tracker daemon
tests/                        unit + CLI
protocol.md                   NDJSON contract
scripts/build.sh              mise exec cargo build --release
scripts/test.sh               cargo test + plugin validate
scripts/dev-install.sh        symlink into ~/.config/omarchy/plugins

Develop

mise install
./scripts/test.sh
./scripts/dev-install.sh
./scripts/dev-watch.sh

After QML edits on a symlink install, force a reload with omarchy-shell shell rescanPlugins — inotify does not follow the plugin symlink.

Tracker:

./target/release/omascreentime-track proto
./target/release/omascreentime-track ensure
./target/release/omascreentime-track view
./target/release/omascreentime-track status
./target/release/omascreentime-track resolve
./target/release/omascreentime-track stop

./scripts/test.sh is the gate: unit tests, CLI tests, omarchy plugin validate, and a JSON parse of manifest.json.

Remove

omarchy plugin disable postman.omascreentime
omarchy plugin remove postman.omascreentime --yes
./target/release/omascreentime-track stop || true
rm -rf ~/.local/state/omascreentime
rm -rf "${XDG_RUNTIME_DIR:-/run/user/$UID}/omascreentime"

If you added the optional menu trigger, delete the trigger.omascreentime block from ~/.config/omarchy/extensions/omarchy-menu.jsonc.

A local symlink install (from ./scripts/dev-install.sh) is not a git checkout:

rm -f ~/.config/omarchy/plugins/postman.omascreentime

License

MIT. See LICENSE.