Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/flatpak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Flatpak

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

jobs:
# Cheap, fast job that catches the metadata problems Flathub's reviewers
# reject submissions for, without waiting on a full build.
metadata:
name: Validate metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install validators
run: |
sudo apt-get update
sudo apt-get install -y appstream desktop-file-utils

- name: Validate AppStream metainfo
run: appstreamcli validate --no-net flatpak/io.github.josh_reimer.BibleApp.metainfo.xml

- name: Validate desktop entry
run: desktop-file-validate flatpak/io.github.josh_reimer.BibleApp.desktop

# Tcl + Tk + CPython are all compiled from source, so a cold-cache build is
# long on both architectures.
build-x86_64:
name: Build x86_64
runs-on: ubuntu-latest
timeout-minutes: 90
container:
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
options: --privileged # flatpak-builder needs bubblewrap
steps:
- uses: actions/checkout@v4

- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
manifest-path: flatpak/io.github.josh_reimer.BibleApp.yml
# The action appends the arch, giving artifact "bibleapp-x86_64".
# Downloadable from the run's Artifacts section:
# flatpak install --user bibleapp.flatpak
bundle: bibleapp.flatpak

# Native arm64 runner (free for public repos). Cannot reuse the job above:
# the flatpak-github-actions container image is published for x86_64 only
# ("no matching manifest for linux/arm64/v8"), so flatpak is installed from
# apt and flatpak-builder is driven directly.
build-aarch64:
name: Build aarch64
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- uses: actions/checkout@v4

- name: Allow unprivileged user namespaces
# Ubuntu 24.04 confines unprivileged userns via AppArmor, which stops
# bubblewrap — and therefore flatpak-builder — from building sandboxes.
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Install flatpak
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak remote-add --user --if-not-exists \
flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --user -y flathub \
org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08

# Lets repeat runs skip recompiling modules whose manifest entry is
# unchanged, which is nearly all of the build time.
- name: Cache flatpak-builder state
uses: actions/cache@v4
with:
path: .flatpak-builder
key: flatpak-builder-aarch64-${{ hashFiles('flatpak/io.github.josh_reimer.BibleApp.yml') }}
restore-keys: flatpak-builder-aarch64-

- name: Build
run: |
flatpak-builder --user --disable-rofiles-fuse --force-clean \
--repo=repo build-dir flatpak/io.github.josh_reimer.BibleApp.yml

- name: Bundle
run: |
flatpak build-bundle repo bibleapp-aarch64.flatpak \
io.github.josh_reimer.BibleApp

- uses: actions/upload-artifact@v4
with:
name: bibleapp-aarch64
path: bibleapp-aarch64.flatpak
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ lib64
bin
include
bibleSearchResult.txt
.gitignore
.gitignore
settings.json
# flatpak-builder artifacts
.flatpak-builder/
build-dir/
repo/
90 changes: 90 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Running the app

```bash
python3 bible-gui-tk.py
```

Single-file Tkinter GUI, stdlib only. No build step, no test suite, no dependency manifest. Two virtualenvs are checked out but gitignored: `.venv` (3.12) and `venv` (3.14) — neither is required, system `python3` with tkinter works.

The only optional dependency is `pyobjc` (`AppKit`), used solely by `_set_dock_icon()` on macOS to set the dock icon from `black-bible.png`. It is wrapped in a bare `except`, so its absence is silent and harmless.

## Data model

The Bible is stored as 66 plaintext files in the repo root, one per book (`genesis.txt` … `revelation.txt`), plus `Bible.txt` which is the whole KJV concatenated (loaded by `go_home()` at startup — ~4.3 MB into a single `tk.Text`).

Line format inside a book file:

```
<chapter>:<verse>: <text>
```

Each book begins with a few non-verse header lines (title, book name, blank lines). Code that parses these files must tolerate them. Two consequences already baked into the code:

- `ChapterVerse.get_chap()` filters by `verse.split(":")[0] == str(chap)` — header lines have no leading number so they fall out naturally.
- `get_selected_book()` derives the chapter count from `int(lines[-1].split(':')[0])`, i.e. it assumes the **last line of every book file is a verse**. Trailing blank lines or appended notes would break the chapter dropdown.

Verse numbers are 1-based in the UI; `get_verse()` indexes `chapter[ver - 1]`.

## Book name / filename mapping

Three parallel lists must stay index-aligned:

- `books_titled` in `bible-gui-tk.py` — display names in the book combobox
- `books_file_names` in `bible-gui-tk.py` — the corresponding `.txt` filenames
- `files` in `BibleFileNames.py` — same filename list, used as the default search corpus

`get_selected_book_from_dropdown()` maps display → file purely by list position (`books_file_names[books_titled.index(...)]`), so inserting into one list without the others silently returns the wrong book.

**Misspellings are canonical.** The lists use `philipians.txt`, `eccliasiastes.txt`, `ezekial.txt`, `first_thesselonians.txt`, `second_thesselonians.txt`. Correctly-spelled duplicates (`philippians.txt`, `first_thessalonians.txt`, `second_thessalonians.txt`) also exist on disk but are **not** referenced. Don't "fix" a name in one place only; renaming requires touching both files and the data file itself.

`OT_FILES` / `NT_FILES` are slices of `books_file_names` at index 39 (Malachi/Matthew boundary) — they depend on the list staying in canonical Bible order.

## Path handling

Every data-file access goes through `_bpath(filename)`, which joins against `SCRIPT_DIR` (the directory of `bible-gui-tk.py`). This is what makes the app runnable from any cwd — new file reads should use it rather than bare relative paths.

Note the inconsistency: `get_selected_book_from_dropdown()` returns an already-absolute path, while `get_search_files()` may return either those absolute paths (scope = "Current Book") or bare filenames from `files`/`OT_FILES`/`NT_FILES`. `linearsearch()` re-wraps everything in `_bpath()`, which happens to be idempotent for absolute paths. Preserve that property if you refactor.

## Theming

`THEMES` is a dict of named palettes with a fixed key set (`bg`, `fg`, `text_bg`, `text_fg`, `entry_bg`, `entry_fg`, `btn_bg`, `sel_bg`, `sel_fg`). Adding a theme means adding one entry with **all** keys — `_theme_widget()` indexes them unconditionally.

`_theme_widget()` walks the widget tree recursively and dispatches on `winfo_class()`. Classic `tk` widgets are recolored per-class there; `ttk` widgets (Combobox, Button, Scrollbar) can't be, so they're styled through `ttk.Style()` in `apply_theme()`. `ttk.Style().theme_use("clam")` is set at import time precisely because the native macOS/Windows ttk themes ignore color overrides — don't remove it. `Toplevel` is deliberately skipped so dialogs keep native chrome.

If you add a new widget kind, it needs a branch in `_theme_widget()` (tk) or a `style.configure` call (ttk), or it will stay unthemed.

## Settings persistence

`settings.json` holds `theme`, `font_family`, `font_size` and lives at `$XDG_CONFIG_HOME/bibleapp/settings.json` (default `~/.config/bibleapp/`), resolved by `_config_path()`. It is **not** next to the script: packaged installs mount the program directory read-only, so writes there fail silently. `_config_path()` falls back to the old in-tree location only if the config dir can't be created.

`load_settings()` tries `CONFIG_FILE` then `LEGACY_CONFIG` (the old in-tree path), so existing users keep their theme on first run after upgrading; the first readable file wins and the rest are skipped. It swallows missing/corrupt files and falls back to defaults. It is called *after* `go_home()` and *before* `apply_theme()` at startup. Settings are written only when the user hits Apply/OK in the settings dialog — font-size changes from the View menu are not persisted.

## Read-only text widget

`bible_text_widget` is a real `tk.Text` (not disabled), kept read-only by `_block_text_edit()`, which returns `"break"` for every key except navigation keys and Ctrl/Cmd+C / Ctrl/Cmd+A. Disabling the widget instead would break selection and copy, which is why it's done this way.

## Helper modules

`list_and_str_ops.py`, `charEliminator.py`, and `ChapterVerse.py` all carry "DO NOT DELETE" banners. Only `ChapterVerse` is actually imported by the current GUI (`list_and_str_ops` is imported as `list`, shadowing the builtin, but unused; `charEliminator` is not imported at all). Leave them in place — the older copy of the app under `Untitled/` still uses them.

`Untitled/` is an untracked snapshot of the pre-refactor app (no themes, no settings, relative paths). It is not the live code; edits belong in the repo root.

## Flatpak packaging

`flatpak/` holds the manifest, launcher, `.desktop` entry, and AppStream metainfo. App ID is `io.github.josh_reimer.BibleApp` (Flatpak IDs can't contain hyphens, so the GitHub username's `-` becomes `_`); the `.desktop` and metainfo filenames must keep matching that ID exactly.

```bash
flatpak install -y flathub org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08
flatpak-builder --user --install --force-clean build-dir flatpak/io.github.josh_reimer.BibleApp.yml
flatpak run io.github.josh_reimer.BibleApp
```

The runtime ships Python but **not** Tkinter, so the manifest builds Tcl, Tk, and CPython. The load-bearing detail: Python 3.12 removed `--with-tcltk-includes`/`--with-tcltk-libs` and finds Tk only via pkg-config (`tcl >= 8.5.12 tk >= 8.5.12`), which is why `PKG_CONFIG_PATH=/app/lib/pkgconfig` is set on the `python3` module and why Tcl/Tk must install their `.pc` files first. A `post-install` step runs `import tkinter` so a broken chain fails the build instead of shipping an app that can't open a window.

Files install to `/app/share/bibleapp/` (read-only), which is why settings use the XDG path above. The app module builds the working tree via `type: dir`; the Flathub copy must pin a git tag + commit instead.

`.github/workflows/flatpak.yml` builds the manifest on x86_64 and aarch64 and uploads installable `.flatpak` bundles as run artifacts — the only way to test this from a non-Linux machine. A separate fast `metadata` job runs `appstreamcli validate` and `desktop-file-validate`, which are the checks Flathub submissions most often fail.
29 changes: 23 additions & 6 deletions bible-gui-tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,37 @@ def _set_dock_icon():
_font_size = tk.IntVar(value=11)
_current_theme = tk.StringVar(value="Light")

CONFIG_FILE = _bpath("settings.json")
def _config_path():
"""Settings live in the user's config dir, not next to the script.

Packaged installs (Flatpak, distro packages) mount the program directory
read-only, so writing settings.json there would silently fail.
"""
base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
folder = os.path.join(base, "bibleapp")
try:
os.makedirs(folder, exist_ok=True)
except OSError:
return _bpath("settings.json") # nothing writable — keep the old spot
return os.path.join(folder, "settings.json")

CONFIG_FILE = _config_path()
LEGACY_CONFIG = _bpath("settings.json") # pre-XDG location, still read once

def load_settings():
try:
with open(CONFIG_FILE) as f:
s = json.load(f)
for path in (CONFIG_FILE, LEGACY_CONFIG):
try:
with open(path) as f:
s = json.load(f)
except (OSError, json.JSONDecodeError):
continue # missing or corrupt — try the next one, else defaults
if s.get("theme") in THEMES:
_current_theme.set(s["theme"])
if s.get("font_family"):
_font_family.set(s["font_family"])
if isinstance(s.get("font_size"), int):
_font_size.set(s["font_size"])
except (FileNotFoundError, json.JSONDecodeError, KeyError):
pass # first run or corrupt file — use defaults
return

def save_settings():
try:
Expand Down
5 changes: 5 additions & 0 deletions flatpak/bibleapp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
# Launcher installed as /app/bin/bibleapp.
# Python puts the script's own directory on sys.path, so the sibling modules
# (ChapterVerse, BibleFileNames, ...) and the .txt data resolve via _bpath().
exec /app/bin/python3 /app/share/bibleapp/bible-gui-tk.py "$@"
9 changes: 9 additions & 0 deletions flatpak/io.github.josh_reimer.BibleApp.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Desktop Bible
Comment=Read and search the Bible offline
Exec=bibleapp
Icon=io.github.josh_reimer.BibleApp
Terminal=false
Categories=Education;Spirituality;Literature;Viewer;
Keywords=Bible;Scripture;KJV;Verse;
58 changes: 58 additions & 0 deletions flatpak/io.github.josh_reimer.BibleApp.metainfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.josh_reimer.BibleApp</id>

<name>Desktop Bible</name>
<summary>Read and search the Bible offline</summary>

<metadata_license>CC0-1.0</metadata_license>
<!-- TODO before submitting to Flathub: this repository has no LICENSE file,
so the code defaults to all-rights-reserved, which is what the value
below records. Add a LICENSE and replace this with its SPDX id
(for example MIT or GPL-3.0-only). The KJV text itself is public
domain, so only your code needs a license. -->
<project_license>LicenseRef-proprietary</project_license>

<developer id="io.github.josh_reimer">
<name>Josh Reimer</name>
</developer>

<description>
<p>
A simple desktop Bible. The complete King James Version is bundled with
the application, so it works with no network connection.
</p>
<ul>
<li>Navigate by book, chapter, and verse</li>
<li>Search the whole Bible, one testament, or just the current book</li>
<li>Optional whole-word matching</li>
<li>Light, Dark, Sepia, and High Contrast themes</li>
<li>Adjustable reading font and size, remembered between sessions</li>
</ul>
</description>

<launchable type="desktop-id">io.github.josh_reimer.BibleApp.desktop</launchable>

<!-- Flathub requires at least one screenshot. These URLs come from the
README; if the review bot cannot fetch them, commit the PNGs to the
repo and point at their raw.githubusercontent.com URLs instead. -->
<screenshots>
<screenshot type="default">
<caption>Reading and searching scripture</caption>
<image>https://github.com/user-attachments/assets/b2b31fe7-78a5-4cda-ba50-cac73d1167a1</image>
</screenshot>
</screenshots>

<url type="homepage">https://github.com/Josh-Reimer/BibleApp-linux</url>
<url type="bugtracker">https://github.com/Josh-Reimer/BibleApp-linux/issues</url>

<content_rating type="oars-1.1" />

<releases>
<release version="1.0.0" date="2026-07-30">
<description>
<p>First Flatpak release.</p>
</description>
</release>
</releases>
</component>
Loading
Loading