Files
OmarchyClockBar/README.md
T
HarveyandClaude Opus 5 460217d059 Let the timezone picker be searched by country
Finding a zone meant already knowing its name, which is the wrong way round
when the thing you know is the country -- Ghana's zone is named after Accra,
and nothing in the picker said so.

Zones now carry the countries they cover, drawn from tzdata's own tables:
zone1970.tab and zone.tab for the zone-to-country-code rows, iso3166.tab for
the names. The picker already matched on description as well as label, so
putting the countries there makes them searchable and shows them in the list.

Those tables only name about 420 of the ~600 zones; the rest are
backward-compatibility aliases like America/Buenos_Aires and US/Pacific, which
tzdata ships as hard links to their canonical zone. Matching an alias to its
canonical name by inode lifts coverage to 553, and the 45 left are the Etc/GMT
pseudo-zones, which have no country by definition.

The inherited countries are the union of every zone sharing the inode, not the
first one found: those siblings are one clock under several countries' names,
and each table names a different subset, so first-match made Africa/Asmera
findable as Ethiopia but not as Eritrea.

Joining is done in ClockModel against a fixture, so it does not depend on the
host's tzdata version; the shell side only reads the files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MhxtL7WzL5XmuTd8GHbcTY
2026-09-04 20:13:11 +02:00

158 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Clock bar
A second bar along the bottom edge of every screen, carrying world clocks laid
out as **one timeline**: furthest behind your local time at the far left,
furthest ahead at the far right, your own time dead centre.
```
LA 08:24 NYC 11:24 LDN 16:24 17:24 SG 23:24 TOK 00:24 +1 AUS 01:24 +1 NZ 03:24 +1
```
An Omarchy 4 shell plugin (Quickshell). It reserves its own space, so Hyprland
tiles windows above it exactly as it does for the top bar.
## What it does
- **Timeline layout.** Side and position are derived from each zone's live UTC
offset, not from config order. Reading left to right always moves forward in
time, and a DST shift re-sorts the bar on its own.
- **24-hour times**, with `+1` / `-1` against clocks on a different date to you.
- **Labels** to the left of each time. The centre clock is your local time and
carries no label.
- **Dimmed outside working hours**, evaluated in each clock's *own* timezone
(weekends included), so a glance tells you who is actually around.
- **Seconds per clock.** Any clock can show `HH:MM:SS` on its own, including
your local one. The bar only drops to a per-second tick when something is
actually displaying seconds.
- **Themed.** Colours, fonts, spacing and bar height all come from the active
Omarchy theme; it follows a theme switch with no restart. Set your own bar
height and the type and spacing scale with it.
## Install
On any Omarchy 4 machine:
```bash
omarchy plugin add https://git.nerdnation.co.za/NerdNation/OmarchyClockBar.git --enable --yes
```
The plugin lands in `~/.config/omarchy/plugins/nerdnation.clockbar/`, and
`~/.config/omarchy/clockbar.json` is seeded on first run.
To update it later, and to remove it:
```bash
omarchy plugin update nerdnation.clockbar
omarchy plugin remove nerdnation.clockbar
```
> Plugins run as unsandboxed QML inside `omarchy-shell`, so `omarchy plugin add`
> clones disabled by default and wants you to read the code first. `--enable`
> skips that.
## Configuring it
Two routes, one source of truth — both read and write
`~/.config/omarchy/clockbar.json`, and the bar reloads from the file either way.
**Click the bar.** Opens an editor with a searchable timezone picker: add,
rename, retime and remove clocks, set seconds per clock, and set the
working-hours window and bar height. Each row shows that zone's current offset
so you can see the effect before saving.
The picker searches **country names as well as zone names**, so you can find a
zone without knowing it is named after a city — typing `Ghana` finds
`Africa/Accra`. Country data is tzdata's own (`zone1970.tab`, `zone.tab`,
`iso3166.tab`); the legacy alias zones tzdata ships as hard links inherit the
countries of the canonical zone they point at.
**Edit the file.** It hot-reloads on save, no restart:
```json
{
"version": 1,
"clocks": [
{ "label": "LA", "timezone": "America/Los_Angeles" },
{ "label": "NYC", "timezone": "America/New_York" },
{ "label": "SG", "timezone": "Asia/Singapore" },
{ "label": "AUS", "timezone": "Australia/Sydney" },
{ "label": "NZ", "timezone": "Pacific/Auckland", "showSeconds": true }
],
"workingHours": { "start": 8, "end": 18, "workdays": [1, 2, 3, 4, 5] },
"dimOutsideWorkingHours": true,
"showSeconds": false,
"local": { "showSeconds": false },
"barSize": 0
}
```
| Key | Meaning |
|---|---|
| `clocks[].label` | Shown left of the time. Defaults to the last part of the zone name. |
| `clocks[].timezone` | Any IANA zone (`timedatectl list-timezones`). |
| `clocks[].showSeconds` | Seconds on this clock. Omit it to inherit the bar-wide default below. |
| `workingHours.start` / `.end` | Hours, 0–24, in each clock's own zone. A window that wraps midnight (`22`–`6`) is understood. |
| `workingHours.workdays` | Days counted as working. `0` = Sunday. |
| `dimOutsideWorkingHours` | Mute clocks outside that window. |
| `showSeconds` | The bar-wide default for seconds, for clocks that do not set their own. |
| `local.showSeconds` | Seconds on your local clock, which has no entry in `clocks[]`. |
| `barSize` | Bar height in px. `0` follows the theme's. Otherwise 12–120; text and spacing scale with it. |
Seconds resolve per clock: a clock's own `showSeconds` wins, otherwise the
bar-wide default applies. An explicit `false` on a clock beats a `true`
default. The bar ticks once a minute unless something on it is showing
seconds — nothing pays for a per-second redraw it does not use.
There is no `left` / `right` setting: which side a clock lands on is a fact
about its offset, not a preference.
**When something is wrong**, the bar says so rather than lying. A zone tzdata
cannot resolve renders as `LABEL --:--` in the theme's urgent colour instead of
silently falling back to UTC. A `clockbar.json` that will not parse turns the
bar's top hairline red and puts `config error` beside your local time, while
the last good config keeps ticking — and the broken file is never overwritten.
## IPC
```bash
omarchy-shell nerdnation.clockbar edit # open the editor
omarchy-shell nerdnation.clockbar toggle
omarchy-shell nerdnation.clockbar refresh # re-probe timezone offsets
omarchy-shell nerdnation.clockbar reload # re-read clockbar.json
```
Bind the editor to a key in `~/.config/hypr/bindings.lua` if you want one.
## How it works, and why
Qt's QML engine ships **no `Intl`**, so there is no way to render an IANA
timezone from inside QML — the usual `Intl.DateTimeFormat({ timeZone })` route
does not exist. Rather than bundle a copy of the tz database, the plugin asks
the system's own tzdata for each zone's current UTC offset (`TZ=<zone> date +%z`)
and then does the arithmetic itself. That keeps it correct across DST changes
and tzdata updates without knowing any of the rules.
The probe is one short-lived shell, and its answer stays valid until a
transition, so it runs on a slow timer — every ten minutes, on each hour
boundary (where transitions land), and whenever the config changes. In between,
the clocks advance by arithmetic on the minute tick.
`ClockModel.js` holds all of that logic and touches no QML types, so it runs
unchanged under node:
```bash
node tests/model.test.js
```
## Layout
| File | Role |
|---|---|
| `manifest.json` | Plugin manifest — `kind: panel`, `keepLoaded` |
| `Clockbar.qml` | Entry point: the bottom layer-shell surface, one per screen |
| `Editor.qml` | The click-to-edit popup |
| `Config.qml` | `clockbar.json` — read, watch, write |
| `Zones.qml` | Timezone offset probe and the zone list for the picker |
| `ClockModel.js` | Ordering, formatting, day offsets, working hours (pure JS) |
| `tests/model.test.js` | Unit tests for the above |