horologion was the wrong book

The Horologion is the Book of Hours: the fixed portions of the services
themselves. This shows the day's commemorations, its fast and its season,
which is a Menologion. Naming it for a book whose contents it does not carry
was a real error and worth the churn to correct.

typikon already uses the word internally, for menologion.py and the data file
it loads. That is not a collision: typikon provides a menologion, this
displays one.

Renamed throughout, not just the repo: the two scripts, the QML component and
its three qmldir modules, MENOLOGION_COLORS, ~/.config/menologion/colors.json,
the eww widgets and scss variables, the waybar class. Verified standalone and
embedded in a foreign shell, and the smoke suite still passes.

Also drops the em dashes from the prose.
This commit is contained in:
funkpower
2026-09-18 10:52:56 -04:00
parent 7beb53fc94
commit be81c682cb
16 changed files with 133 additions and 134 deletions
+29 -30
View File
@@ -1,11 +1,11 @@
# horologion # menologion
Desktop front ends for [`typikon`](https://git.pumpin.online/funkpower/typikon) Desktop front ends for [`typikon`](https://git.pumpin.online/funkpower/typikon).
— the Orthodox liturgical day on your desktop, in Hyprland (Quickshell), The Orthodox liturgical day on your desktop, in Hyprland (Quickshell), waybar
waybar or eww. or eww.
Everything is computed locally by `typikon`, so none of this touches the Everything is computed locally by `typikon`, so none of this touches the
network, and the widget re-reads only when the date rolls over — the answer network, and the widget re-reads only when the date rolls over. The answer
changes at midnight and at no other moment. changes at midnight and at no other moment.
![The widget during the Nativity Fast](docs/nativity-fast.png) ![The widget during the Nativity Fast](docs/nativity-fast.png)
@@ -22,8 +22,8 @@ $ typikon --set-default rocor # or antiochian, oca, greece, ...
Then: Then:
```console ```console
$ git clone https://git.pumpin.online/funkpower/horologion $ git clone https://git.pumpin.online/funkpower/menologion
$ cd horologion && ./bin/horologion-install $ cd menologion && ./bin/menologion-install
``` ```
That links rather than copies, so `git pull` updates what you are running. Pass That links rather than copies, so `git pull` updates what you are running. Pass
@@ -34,19 +34,19 @@ That links rather than copies, so `git pull` updates what you are running. Pass
A desktop widget for Hyprland and anything else Quickshell runs on. A desktop widget for Hyprland and anything else Quickshell runs on.
```console ```console
$ quickshell -p ~/.config/quickshell/horologion/shell.qml $ quickshell -p ~/.config/quickshell/menologion/shell.qml
``` ```
It sits on the background layer, so it behaves like part of the wallpaper — It sits on the background layer, so it behaves like part of the wallpaper:
always under your windows, never stealing a click. always under your windows, never stealing a click.
To drop it into a shell you already have, link this directory in beside your To drop it into a shell you already have, link this directory in beside your
own and import it by path: own and import it by path:
```qml ```qml
import "horologion/modules" import "menologion/modules"
Horologion {} Menologion {}
``` ```
The imports inside are relative, so the widget always resolves its own The imports inside are relative, so the widget always resolves its own
@@ -57,16 +57,16 @@ Three pieces, each usable on its own:
| | | | | |
|---|---| |---|---|
| `services/Typikon.qml` | a singleton wrapping the CLI — bind to it from anything | | `services/Typikon.qml` | a singleton wrapping the CLI; bind to it from anything |
| `services/Theme.qml` | colours, from JSON, with defaults for every key | | `services/Theme.qml` | colours, from JSON, with defaults for every key |
| `modules/Horologion.qml` | the widget | | `modules/Menologion.qml` | the widget |
Set a jurisdiction per-widget if you want one different from your default: Set a jurisdiction per-widget if you want one different from your default:
```qml ```qml
import "horologion/services" // for Typikon import "menologion/services" // for Typikon
Horologion { Menologion {
Component.onCompleted: Typikon.jurisdiction = "moscow" Component.onCompleted: Typikon.jurisdiction = "moscow"
} }
``` ```
@@ -76,25 +76,24 @@ you look at what Great Week will ask of you before it arrives.
### Colours ### Colours
`Theme.qml` reads `~/.config/horologion/colors.json`, or whatever `Theme.qml` reads `~/.config/menologion/colors.json`, or whatever
`HOROLOGION_COLORS` points at, and watches it for changes. Every key has a `MENOLOGION_COLORS` points at, and watches it for changes. Every key has a
default, so the widget is legible with no file at all. See default, so the widget is legible with no file at all. See
`quickshell/colors.example.json` — the flat shape most theme generators `quickshell/colors.example.json`, the flat shape most theme generators
already emit. already emit.
## waybar ## waybar
```jsonc ```jsonc
"custom/typikon": { "custom/typikon": {
"exec": "horologion-waybar", "exec": "menologion-waybar",
"return-type": "json", "return-type": "json",
"interval": 1800, "interval": 1800,
"on-click": "typikon --days 7" "on-click": "typikon --days 7"
} }
``` ```
The module's CSS class is the fast level — `.xerophagy`, `.wine_oil`, `.fish` The module's CSS class is the fast level (`.xerophagy`, `.wine_oil`, `.fish`), so a day's discipline reads off the colour without reading the text.
— so a day's discipline reads off the colour without reading the text.
`.obligatory` is added only where a particular law actually binds, which under `.obligatory` is added only where a particular law actually binds, which under
the Typikon is never: the Orthodox Church keeps the fast as tradition, not as the Typikon is never: the Orthodox Church keeps the fast as tradition, not as
precept. `waybar/style.css` has a starting palette. precept. `waybar/style.css` has a starting palette.
@@ -102,18 +101,18 @@ precept. `waybar/style.css` has a starting palette.
Change what the text says with `--format`: Change what the text says with `--format`:
```console ```console
$ horologion-waybar --format '{emoji} {fastLabel}' $ menologion-waybar --format '{emoji} {fastLabel}'
$ horologion-waybar -j moscow --format '{emoji} {churchDate}' $ menologion-waybar -j moscow --format '{emoji} {churchDate}'
``` ```
## eww ## eww
```lisp ```lisp
(include "horologion/horologion.yuck") (include "menologion/menologion.yuck")
``` ```
Then `(horologion)` for the full panel or `(horologion-bar)` for one line. Then `(menologion)` for the full panel or `(menologion-bar)` for one line.
`horologion.scss` carries the styling; its colours are `!default` variables, so `menologion.scss` carries the styling; its colours are `!default` variables, so
set your own before the `@import`. set your own before the `@import`.
## What it shows ## What it shows
@@ -123,7 +122,7 @@ the week ahead, and Pascha.
**The church date is always in the corner.** On the new calendar it echoes the **The church date is always in the corner.** On the new calendar it echoes the
wall calendar; on the old it is the answer you came for. Below, ROCOR on 7 wall calendar; on the old it is the answer you came for. Below, ROCOR on 7
January — which is 25 December, and the Nativity. January, which is 25 December, and the Nativity.
![The Nativity on the old calendar](docs/old-calendar.png) ![The Nativity on the old calendar](docs/old-calendar.png)
@@ -133,12 +132,12 @@ days at a time, and a widget that leads with "Post-festive Day of the
Exaltation" for a week has told you nothing. Exaltation" for a week has told you nothing.
**The legend is built from the week in view**, not hardcoded, because the **The legend is built from the week in view**, not hardcoded, because the
levels a tradition uses are its own — six under the Typikon, three under levels a tradition uses are its own: six under the Typikon, three under
Ruthenian particular law — and the words for them differ too. Great Week uses Ruthenian particular law, and the words for them differ too. Great Week uses
nearly all of them: nearly all of them:
![Great and Holy Week](docs/great-week.png) ![Great and Holy Week](docs/great-week.png)
## Licence ## Licence
GPL-2.0-only — see [LICENSE](LICENSE). GPL-2.0-only. See [LICENSE](LICENSE).
@@ -9,12 +9,12 @@ binhome="${HOME}/.local/bin"
usage() { usage() {
cat <<USAGE cat <<USAGE
horologion-install [quickshell|waybar|eww|bin|all] menologion-install [quickshell|waybar|eww|bin|all]
quickshell link quickshell/ into a quickshell config as horologion/ quickshell link quickshell/ into a quickshell config as menologion/
waybar link the bar script and print the module to merge waybar link the bar script and print the module to merge
eww link eww/ into your eww config eww link eww/ into your eww config
bin link horologion-waybar into ~/.local/bin bin link menologion-waybar into ~/.local/bin
all bin + whichever of the three are already configured all bin + whichever of the three are already configured
USAGE USAGE
@@ -37,7 +37,7 @@ link() {
} }
do_bin() { do_bin() {
link "$src/bin/horologion-waybar" "$binhome/horologion-waybar" link "$src/bin/menologion-waybar" "$binhome/menologion-waybar"
case ":$PATH:" in case ":$PATH:" in
*":$binhome:"*) ;; *":$binhome:"*) ;;
*) echo " note: $binhome is not on your PATH" >&2 ;; *) echo " note: $binhome is not on your PATH" >&2 ;;
@@ -45,43 +45,43 @@ do_bin() {
} }
do_quickshell() { do_quickshell() {
link "$src/quickshell" "$config/quickshell/horologion" link "$src/quickshell" "$config/quickshell/menologion"
cat <<'NOTE' cat <<'NOTE'
Add to your shell.qml: Add to your shell.qml:
import qs.horologion.modules import qs.menologion.modules
... ...
Horologion {} Menologion {}
Or run it on its own: Or run it on its own:
quickshell -p ~/.config/quickshell/horologion/shell.qml quickshell -p ~/.config/quickshell/menologion/shell.qml
NOTE NOTE
} }
do_waybar() { do_waybar() {
link "$src/waybar/style.css" "$config/waybar/horologion.css" link "$src/waybar/style.css" "$config/waybar/menologion.css"
cat <<'NOTE' cat <<'NOTE'
Add "custom/typikon" to a modules list, merge waybar/config.jsonc into Add "custom/typikon" to a modules list, merge waybar/config.jsonc into
your config, and @import "horologion.css" from your style.css. your config, and @import "menologion.css" from your style.css.
NOTE NOTE
} }
do_eww() { do_eww() {
link "$src/eww" "$config/eww/horologion" link "$src/eww" "$config/eww/menologion"
cat <<'NOTE' cat <<'NOTE'
Add to your eww.yuck: (include "horologion/horologion.yuck") Add to your eww.yuck: (include "menologion/menologion.yuck")
Add to your eww.scss: @import "horologion/horologion.scss"; Add to your eww.scss: @import "menologion/menologion.scss";
Then put (horologion) in a window, or (horologion-bar) in a bar. Then put (menologion) in a window, or (menologion-bar) in a bar.
NOTE NOTE
} }
require_typikon() { require_typikon() {
command -v typikon >/dev/null 2>&1 && return 0 command -v typikon >/dev/null 2>&1 && return 0
echo "horologion: typikon is not on PATH." >&2 echo "menologion: typikon is not on PATH." >&2
echo " pipx install git+https://git.pumpin.online/funkpower/typikon" >&2 echo " pipx install git+https://git.pumpin.online/funkpower/typikon" >&2
exit 1 exit 1
} }
@@ -2,7 +2,7 @@
"""waybar custom module — the liturgical day in the bar. """waybar custom module — the liturgical day in the bar.
"custom/typikon": { "custom/typikon": {
"exec": "horologion-waybar", "exec": "menologion-waybar",
"return-type": "json", "return-type": "json",
"interval": 1800, "interval": 1800,
"on-click": "typikon --days 7" "on-click": "typikon --days 7"
-62
View File
@@ -1,62 +0,0 @@
// horologion — colours are variables so you can point them at your own theme.
// Defaults are a neutral dark set; override them before the @import.
$horologion-fg: #ebdbb2 !default;
$horologion-fg-dim: #a89984 !default;
$horologion-accent: #d3869b !default;
$horologion-red: #fb4934 !default;
$horologion-yellow: #fabd2f !default;
$horologion-surface: #282828 !default;
$horologion-overlay: #504945 !default;
.horologion {
background-color: rgba(40, 40, 40, 0.82);
border: 1px solid $horologion-overlay;
border-radius: 14px;
padding: 14px;
color: $horologion-fg;
min-width: 400px;
.glyph-big { font-size: 28px; }
.glyph { font-size: 15px; }
.title {
font-weight: bold;
font-size: 13px;
&.feast { color: $horologion-accent; }
}
.sub {
font-size: 10px;
color: $horologion-yellow;
&.obligatory { color: $horologion-red; font-weight: bold; }
}
.cycle {
font-size: 10px;
font-style: italic;
color: rgba(168, 153, 132, 0.8);
}
.fast { font-size: 11px; color: $horologion-fg-dim; }
.rule {
background-color: rgba(80, 73, 69, 0.6);
min-height: 1px;
}
.weekday { font-size: 11px; color: $horologion-yellow; }
.day {
font-size: 10px;
color: $horologion-fg-dim;
&.feast { color: $horologion-accent; }
}
.pascha { font-size: 10px; color: $horologion-fg-dim; }
}
.horologion-bar {
.glyph { font-size: 14px; }
.title { font-size: 12px; }
}
+62
View File
@@ -0,0 +1,62 @@
// menologion — colours are variables so you can point them at your own theme.
// Defaults are a neutral dark set; override them before the @import.
$menologion-fg: #ebdbb2 !default;
$menologion-fg-dim: #a89984 !default;
$menologion-accent: #d3869b !default;
$menologion-red: #fb4934 !default;
$menologion-yellow: #fabd2f !default;
$menologion-surface: #282828 !default;
$menologion-overlay: #504945 !default;
.menologion {
background-color: rgba(40, 40, 40, 0.82);
border: 1px solid $menologion-overlay;
border-radius: 14px;
padding: 14px;
color: $menologion-fg;
min-width: 400px;
.glyph-big { font-size: 28px; }
.glyph { font-size: 15px; }
.title {
font-weight: bold;
font-size: 13px;
&.feast { color: $menologion-accent; }
}
.sub {
font-size: 10px;
color: $menologion-yellow;
&.obligatory { color: $menologion-red; font-weight: bold; }
}
.cycle {
font-size: 10px;
font-style: italic;
color: rgba(168, 153, 132, 0.8);
}
.fast { font-size: 11px; color: $menologion-fg-dim; }
.rule {
background-color: rgba(80, 73, 69, 0.6);
min-height: 1px;
}
.weekday { font-size: 11px; color: $menologion-yellow; }
.day {
font-size: 10px;
color: $menologion-fg-dim;
&.feast { color: $menologion-accent; }
}
.pascha { font-size: 10px; color: $menologion-fg-dim; }
}
.menologion-bar {
.glyph { font-size: 14px; }
.title { font-size: 12px; }
}
+7 -7
View File
@@ -1,10 +1,10 @@
;; horologion — the liturgical day for eww ;; menologion — the liturgical day for eww
;; ;;
;; Include this from your own eww.yuck: ;; Include this from your own eww.yuck:
;; ;;
;; (include "horologion/horologion.yuck") ;; (include "menologion/menologion.yuck")
;; ;;
;; and put (horologion) in a window, or (horologion-bar) in a bar. Set a ;; and put (menologion) in a window, or (menologion-bar) in a bar. Set a
;; jurisdiction by adding `--jurisdiction <name>` to both defpoll commands ;; jurisdiction by adding `--jurisdiction <name>` to both defpoll commands
;; below; leaving them as they are follows whatever `typikon --set-default` ;; below; leaving them as they are follows whatever `typikon --set-default`
;; wrote. ;; wrote.
@@ -22,8 +22,8 @@
`typikon --json --days 6 --date $(date -d tomorrow +%F)`) `typikon --json --days 6 --date $(date -d tomorrow +%F)`)
;; ── the one-line version, for a bar ;; ── the one-line version, for a bar
(defwidget horologion-bar [] (defwidget menologion-bar []
(box :class "horologion-bar" (box :class "menologion-bar"
:space-evenly false :space-evenly false
:spacing 6 :spacing 6
(label :class "glyph" :text {typikon-day.emoji ?: ""}) (label :class "glyph" :text {typikon-day.emoji ?: ""})
@@ -32,8 +32,8 @@
:text {typikon-day.title ?: "typikon?"}))) :text {typikon-day.title ?: "typikon?"})))
;; ── the full panel ;; ── the full panel
(defwidget horologion [] (defwidget menologion []
(box :class "horologion" (box :class "menologion"
:orientation "v" :orientation "v"
:space-evenly false :space-evenly false
:spacing 8 :spacing 8
+2 -2
View File
@@ -1,3 +1,3 @@
module horologion.modules module menologion.modules
Panel 1.0 Panel.qml Panel 1.0 Panel.qml
Horologion 1.0 Horologion.qml Menologion 1.0 Menologion.qml
+1 -1
View File
@@ -1,2 +1,2 @@
module horologion module menologion
singleton Config 1.0 Config.qml singleton Config 1.0 Config.qml
+3 -3
View File
@@ -8,16 +8,16 @@ import Quickshell.Io
// rest of your desktop. // rest of your desktop.
// //
// Every key has a default, so the widget is legible with no file at all. Point // Every key has a default, so the widget is legible with no file at all. Point
// HOROLOGION_COLORS at your own, or drop one at the path below. The shape is // MENOLOGION_COLORS at your own, or drop one at the path below. The shape is
// the flat sixteen-colour kind most theme generators already emit. // the flat sixteen-colour kind most theme generators already emit.
Singleton { Singleton {
id: root id: root
readonly property string path: { readonly property string path: {
const env = Quickshell.env("HOROLOGION_COLORS"); const env = Quickshell.env("MENOLOGION_COLORS");
return env && env !== "" return env && env !== ""
? env ? env
: Quickshell.env("HOME") + "/.config/horologion/colors.json"; : Quickshell.env("HOME") + "/.config/menologion/colors.json";
} }
property var c: ({}) property var c: ({})
+1 -1
View File
@@ -1,3 +1,3 @@
module horologion.services module menologion.services
singleton Typikon 1.0 Typikon.qml singleton Typikon 1.0 Typikon.qml
singleton Theme 1.0 Theme.qml singleton Theme 1.0 Theme.qml
+3 -3
View File
@@ -11,7 +11,7 @@ import "services"
// A runnable widget on its own — `quickshell -p quickshell/shell.qml`. // A runnable widget on its own — `quickshell -p quickshell/shell.qml`.
// //
// It sits on the background layer, so it behaves like part of the wallpaper: // It sits on the background layer, so it behaves like part of the wallpaper:
// always under your windows, never stealing a click. Drop the Horologion // always under your windows, never stealing a click. Drop the Menologion
// component into an existing shell instead if you already have one. // component into an existing shell instead if you already have one.
ShellRoot { ShellRoot {
Variants { Variants {
@@ -22,7 +22,7 @@ ShellRoot {
screen: modelData screen: modelData
WlrLayershell.layer: WlrLayer.Background WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "horologion" WlrLayershell.namespace: "menologion"
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore
color: "transparent" color: "transparent"
@@ -36,7 +36,7 @@ ShellRoot {
id: content id: content
anchors.fill: parent anchors.fill: parent
Horologion { Menologion {
// Leave empty to follow `typikon --set-default`, or name a // Leave empty to follow `typikon --set-default`, or name a
// jurisdiction here: "rocor", "antiochian", "oca", ... // jurisdiction here: "rocor", "antiochian", "oca", ...
Component.onCompleted: Typikon.jurisdiction = "" Component.onCompleted: Typikon.jurisdiction = ""
+2 -2
View File
@@ -22,7 +22,7 @@ ShellRoot {
screen: Quickshell.screens[0] screen: Quickshell.screens[0]
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "horologion-shot" WlrLayershell.namespace: "menologion-shot"
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore
color: "transparent" color: "transparent"
@@ -49,7 +49,7 @@ ShellRoot {
anchors.topMargin: 34 anchors.topMargin: 34
width: Config.width width: Config.width
Horologion { Menologion {
Component.onCompleted: { Component.onCompleted: {
const j = Quickshell.env("SHOT_JURISDICTION"); const j = Quickshell.env("SHOT_JURISDICTION");
const d = Quickshell.env("SHOT_DATE"); const d = Quickshell.env("SHOT_DATE");
+4 -4
View File
@@ -9,8 +9,8 @@
set -uo pipefail set -uo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
waybar="$root/bin/horologion-waybar" waybar="$root/bin/menologion-waybar"
install="$root/bin/horologion-install" install="$root/bin/menologion-install"
pass=0 fail=0 pass=0 fail=0
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT trap 'rm -rf "$tmp"' EXIT
@@ -63,9 +63,9 @@ check "--help without typikon" 0 env PATH=/usr/bin:/bin "$install" --
check "an unknown subcommand" 2 "$install" nonsense check "an unknown subcommand" 2 "$install" nonsense
mkdir -p "$tmp/a/.config/quickshell" "$tmp/a/.config/waybar" mkdir -p "$tmp/a/.config/quickshell" "$tmp/a/.config/waybar"
check "a clean run with eww absent" 0 env HOME="$tmp/a" XDG_CONFIG_HOME="$tmp/a/.config" "$install" all check "a clean run with eww absent" 0 env HOME="$tmp/a" XDG_CONFIG_HOME="$tmp/a/.config" "$install" all
mkdir -p "$tmp/b/.local/bin" "$tmp/b/.config"; : > "$tmp/b/.local/bin/horologion-waybar" mkdir -p "$tmp/b/.local/bin" "$tmp/b/.config"; : > "$tmp/b/.local/bin/menologion-waybar"
check "a real file at the target" 1 env HOME="$tmp/b" XDG_CONFIG_HOME="$tmp/b/.config" "$install" all check "a real file at the target" 1 env HOME="$tmp/b" XDG_CONFIG_HOME="$tmp/b/.config" "$install" all
[ -L "$tmp/a/.local/bin/horologion-waybar" ] \ [ -L "$tmp/a/.local/bin/menologion-waybar" ] \
&& ok "the clean run actually linked" || bad "the clean run actually linked" && ok "the clean run actually linked" || bad "the clean run actually linked"
echo echo
+2 -2
View File
@@ -2,8 +2,8 @@
// "custom/typikon", and merge the block below. // "custom/typikon", and merge the block below.
{ {
"custom/typikon": { "custom/typikon": {
"exec": "horologion-waybar", "exec": "menologion-waybar",
// Add -j to pin a jurisdiction, e.g. "horologion-waybar -j rocor". // Add -j to pin a jurisdiction, e.g. "menologion-waybar -j rocor".
// Without it, whatever `typikon --set-default` wrote is used. // Without it, whatever `typikon --set-default` wrote is used.
"return-type": "json", "return-type": "json",
// The answer changes at midnight and at no other moment, so this is only // The answer changes at midnight and at no other moment, so this is only
+1 -1
View File
@@ -1,4 +1,4 @@
/* horologion — waybar styling for the custom/typikon module. /* menologion — waybar styling for the custom/typikon module.
* *
* The module's class is the fast level, so a day's discipline can be read off * The module's class is the fast level, so a day's discipline can be read off
* the colour without reading the text. `.obligatory` is added only where a * the colour without reading the text. `.obligatory` is added only where a