Linux · Open Source · GPLv3

Quran Player

Verse-by-verse Quran recitation, controlled from your Linux desktop. A background daemon drives the audio; a live Conky overlay keeps the current ayah in front of you — no cloud, no account, no window stealing focus.

v2.1.0 ·Released 2026-10-24 ·Python 3.9+ ·X11 / XWayland ·No root required
Quran Player's Conky overlay showing an Arabic verse on a Linux desktop
The Conky overlay — the current verse, right on the desktop

👤 Who is this for?

Quran Player is a Linux-native background daemon for verse-by-verse Quran audio playback with a live Conky overlay that renders the current verse right on your desktop. It stays in sync — when the verse advances, the overlay updates within half a second.

  • Linux users who want Quran playback integrated into their desktop — not a separate app window
  • Anyone using a tiling WM / minimal setup who prefers a Conky overlay to a GUI
  • People who script their environment and want a programmable daemon over a Unix socket
  • Users on X11 or XWayland (feh-based overlay solutions do not survive there; Conky does)

Looking for a full-featured GUI? See Quran Browser instead — Quran Player is the lean, daemon-native sibling.

✨ Key Features

🖼️

Conky Desktop Overlay

  • Live verse display, updates within 0.5 s
  • Daemon-managed lifecycle (start / stop / toggle)
  • Survives daemon restarts; orphans reaped automatically
  • X11 native; works under XWayland on compatible compositors
🎵

Verse-by-Verse Playback

  • Auto-advance through the entire Quran
  • Repeat modes: single ayah, range, whole surah — cleared by load, ns, ps
  • goto, next, prev, ns, ps
  • Persistent state across sessions
⚙️

Daemon + Unix Socket

  • JSON status for scripting
  • Any language can control it via the socket
  • Socket permissions locked to 0600
  • Atomic lock file, crash-safe PID management
🎛️

Optional GUI Tray

  • PyQt5 tray controller
  • Keyboard: Space / ← / → / Esc
  • Conky toggle button
  • Edit config in-place
🔍

Arabic Search Tool

  • Full-text Uthmani + simplified search
  • Hamza / diacritic normalization
  • Interactive RTL dialog
  • CLI mode for scripting
📦

One-Command Install

  • python install.py — no root
  • Installs under ~/.quran-player/
  • Incremental updates via --clean
  • Clean uninstall with --uninstall

🖥️ Screenshots

Conky overlay showing the current verse on the desktop
Conky overlay — the verse sits on the desktop while you work
Quran Player GUI controller
The optional PyQt5 tray controller

🚀 Install

terminal
# 1. Prerequisites (Debian / Ubuntu — adapt for your distro)
sudo apt install conky-all python3 python3-venv

# 2. Clone and install (no sudo for step 2)
git clone https://github.com/neoMOSAID/quran-player.git
cd quran-player
python install.py

# 3. Start the daemon — Conky launches automatically
quran-daemon start
quran-daemon play

The installer drops everything under ~/.quran-player/ and creates CLI wrappers in ~/.local/bin/ — quran-daemon, quran-gui, and quran-search.

Audio ships with the install. A 19-file sample (Surah 1 complete, Surah 2:1–10) is copied into ~/.config/quran-player/sample/ on first run — so playback works immediately, out of the box. To play the full Quran, download a recitation set from EveryAyah.com (or your own collection) into that directory, or point the daemon at it with quran-daemon dir /path/to/audio.
Other distributions: sudo pacman -S conky (Arch) · sudo dnf install conky (Fedora). For Arabic shaping in Conky, install fonts-hosny-amiri (sudo apt install fonts-hosny-amiri).
Wayland note. Quran Player is developed and tested on X11. Under Wayland, Conky runs through XWayland, and whether its override-type window is honoured depends on the compositor (GNOME and KDE work; some wlroots compositors do not render override windows). Audio playback, the daemon, and the CLI are compositor-agnostic and work everywhere.

📥 Download

Current version: v2.1.0 (released 2026-10-24)

The ZIP archive is a source snapshot of the main branch — install by running python install.py inside the extracted directory. For tagged releases, use the GitHub Releases page.

System requirements: Linux (X11 or XWayland) · Python 3.9+ · Conky · pactl (PulseAudio / PipeWire). No root required.

💻 Usage

terminal
quran-daemon start              # daemon starts + conky launches
quran-daemon play | pause | toggle
quran-daemon next | prev
quran-daemon load 2:255         # surah:ayah
quran-daemon repeat 2:1:20      # repeat surah 2, ayahs 1–20
quran-daemon conky_off          # hide desktop overlay (audio keeps playing)
quran-daemon conky_on           # show it again
quran-daemon status             # JSON status
quran-daemon info               # full diagnostic dump
quran-daemon reload_config      # re-read config.ini without restarting
quran-daemon stop               # daemon + conky both go down

🖥️ Real-world i3 Integration advanced

I use Quran Player as a background service: media keys control playback, while the current verse appears in i3blocks. The media-key example is shown below; optional scripts and advanced configuration are available in the expandable sections. These examples use my personal ~/bin/qd wrapper and can be adapted to your setup.

🔑 Media keys

Drop this into any i3 config file that's included from your main ~/.config/i3/config. The qd wrapper (next section) is what makes these one-liners; the daemon does the actual work.

~/.config/i3/config_files/multimedia.conf
# Media player (Quran / mpd)
bindsym XF86AudioPlay          exec --no-startup-id ~/bin/qd toggle
bindsym shift+XF86AudioPlay    exec --no-startup-id ~/bin/qd conky_toggle
bindsym XF86AudioPause         exec --no-startup-id ~/bin/qd pause
bindsym XF86AudioNext          exec --no-startup-id ~/bin/qd next
bindsym XF86AudioPrev          exec --no-startup-id ~/bin/qd prev
bindsym ctrl+XF86AudioNext     exec --no-startup-id ~/bin/qd ns
bindsym ctrl+XF86AudioPrev     exec --no-startup-id ~/bin/qd ps
bindsym shift+XF86AudioNext    exec --no-startup-id ~/bin/qd ns
bindsym shift+XF86AudioPrev    exec --no-startup-id ~/bin/qd ps
bindsym XF86AudioStop          exec --no-startup-id ~/bin/qd stop
KeyAction
XF86AudioPlayPlay / pause (auto-starts daemon if needed)
Shift + XF86AudioPlayToggle Conky overlay
XF86AudioNext / PrevNext / previous ayah
Ctrl or Shift + Next / PrevNext / previous surah
XF86AudioStopStop the daemon (Conky goes down with it)

🧰 The qd wrapper

The optional qd wrapper can auto-start the daemon for playback commands and refresh the status bar after each invocation. You only need this script if you want the same behavior for your own i3 keybindings.

Show full ~/bin/qd source
~/bin/qd
#!/bin/bash
# qd — Quran Player Daemon control wrapper.
#
# State ownership:
#   - PID file   : ~/.quran-player/control/daemon.pid
#   - Liveness   : ~/.quran-player/control/daemon.lock
#   - Socket     : ~/.quran-player/control/daemon.sock
#
# This wrapper is stateless. It never writes or deletes a PID file.

PY="$HOME/.quran-player/env/bin/python"
DAEMON="$HOME/.quran-player/daemon.py"

daemon_ready() {
    "$PY" "$DAEMON" status >/dev/null 2>&1
}

# Start the daemon if needed, wait until its socket answers.
ensure_running() {
    daemon_ready && return 0
    "$PY" "$DAEMON" start >/dev/null &
    local i
    for i in $(seq 1 30); do
        daemon_ready && return 0
        sleep 0.1
    done
    echo "Error: daemon failed to become ready within 3s" >&2
    echo "       see ~/.quran-player/control/daemon.log" >&2
    return 1
}

# Nudge i3blocks for an instant status refresh. No-op if absent.
refresh_bar() { pkill -RTMIN+8 i3blocks 2>/dev/null || true; }

case "$1" in
    start)
        if daemon_ready; then echo "OK (already running)"; exit 0; fi
        ensure_running && echo "OK" && exit 0
        exit 1
        ;;
    stop)
        if ! daemon_ready; then echo "OK (already stopped)"; exit 0; fi
        "$PY" "$DAEMON" stop
        ;;
    dir)
        shift
        if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
            "$PY" "$DAEMON" dir "-h"; exit 0
        fi
        path="$*"
        [[ "$path" != /* ]] && path="$(realpath "$path")"
        "$PY" "$DAEMON" dir "$path"
        ;;
    [0-9]*)
        ensure_running || exit 1
        "$PY" "$DAEMON" goto "$1"
        ;;
    load|repeat)
        command=$1; shift
        arg=$(IFS=:; echo "$*")
        ensure_running || exit 1
        "$PY" "$DAEMON" "$command" "$arg"
        ;;
    play|resume|toggle|next|prev|ns|ps|goto)
        ensure_running || exit 1
        "$PY" "$DAEMON" "$@"
        ;;
    *)
        # status, info, log, config, cleanup, about, conky_*, pause, stop
        # — do NOT auto-start; they must fail loudly if the daemon is down.
        "$PY" "$DAEMON" "$@"
        ;;
esac

# Ask i3blocks to re-poll on next tick. Silently skipped if absent.
refresh_bar
Configuration editing and advanced commands

✏️ Editing the configuration

The daemon owns the authoritative copy of ~/.config/quran-player/config.ini. You can inspect or edit it without stopping the daemon — every change is validated against the schema before it is written, and unknown keys or malformed values are rejected with a message, not silently dropped.

terminal
quran-daemon config_get         # show current config (with schema comments)
quran-daemon config_defaults    # show all keys at their default values
quran-daemon config_get | less

# Edit + save in one shot:
quran-daemon config_get | sed 's/^# log_level/log_level/' | quran-daemon config_set -

# Nuclear option — overwrite with defaults:
quran-daemon config            # ⚠ wipes any customizations

The Conky display settings are a separate file (conky_config.json) with the same workflow: conky_config, conky_config_defaults, conky_config_set, conky_config_reset.

Customize the i3blocks status bar

📊 Status bar widget (i3blocks)

The current surah and ayah live in the i3blocks status bar. Left-clicking the block opens the GUI; right-clicking toggles play/pause. Requires jq (sudo apt install jq).

Show full ~/.config/i3blocks/ayat.sh
~/.config/i3blocks/ayat.sh
#!/bin/bash
daemon="$HOME/bin/qd"

case $BLOCK_BUTTON in
    1)  # Left click → open GUI
        nohup "$HOME/.local/bin/quran-gui" >/dev/null 2>&1 </dev/null &
        ;;
    3)  # Right click → toggle pause/resume
        $daemon toggle
        ;;
esac

status_data="$($daemon status)"

# Check if daemon is running and playing
if echo "$status_data" | grep -q '"daemon_running": true'; then
    paused=$(echo "$status_data" | jq -r '.paused')
    surah_num=$(echo "$status_data" | jq -r '.surah')
    ayah_num=$(echo "$status_data" | jq -r '.ayah')

    if [[ -n "$surah_num" && "$surah_num" != "null" ]]; then
        surah_name=$("$HOME/.local/bin/quran-search" "$surah_num")

        # Add pause icon if paused
        if [[ "$paused" == "true" ]]; then
            icon="⏸️"
        else
            icon=""
        fi

        # i3blocks convention: full_text then short_text (do not dedupe!)
        echo "$icon $surah_name $ayah_num"
        echo "$icon $surah_name $ayah_num"
        echo "#FFD700"
    fi
fi
Show full ~/.config/i3blocks/config entry
~/.config/i3blocks/config
[ayat]
command=~/.config/i3blocks/ayat.sh
interval=1
signal=8

Tip: signal=8 lets the wrapper force an instant refresh with pkill -RTMIN+8 i3blocks — which the qd script above does automatically at the end of every invocation. With interval=1 the widget polls every second anyway, so the signal is optional and safe to drop if you prefer a quieter setup.

The status block output looks like الفاتحة 3 — surah name + ayah number, with a ⏸️ prefix when paused, rendered in gold (#FFD700) in the bar.

🛠️ Technology

Python 3.9+ Conky pygame-ce PyQt5 (tray) UNIX domain socket portalocker arabic-reshaper + python-bidi

💬 Support & Community

📝 View release history — version 2.1.0

Version history for Quran Player.

# Changelog

2.1.0 — 2026-10-25

•GUI crash fixed: QMainWindow has no setWindowRole(). Three call sites in quran_gui.py (QuranPlayer.__init__, config(), conky_config()) invoked a method that exists only on QWindow, so launching quran-gui raised AttributeError immediately — the tray icon never appeared, the window never opened, and pressing Play through the wrapper just hung waiting for a socket the GUI was supposed to spawn. Removed the three calls; the role hint was never read by any consumer anyway.

•config_set / conky_config_set no longer truncate payloads. The previous read loop broke on chunk.endswith(b"\n"), so any INI file whose first ~4 KB of bytes happened to contain a newline at a recv boundary was silently truncated there and then validated as-is — editing a config with more than a handful of ; comments would wipe everything after the first newline byte that landed in a boundary chunk. The protocol is now: for config_set / conky_config_set, the client sends the first line (config_set\n), then the payload, then shutdown(SHUT_WR). The daemon reads until EOF (64 KB cap, 5 s idle timeout). Both daemon.py and quran_gui.py updated in lockstep. See the *Compatibility notes* below.

•Errors carry their real cause. Every play / pause / play_verse / load / repeat / goto / dir failure now sets self.error_msg before returning False, and the daemon's response format is ERROR: instead of ERROR: Command failed: . When the message is genuinely empty, the daemon now emits ERROR: Command failed (no details) rather than a trailing-whitespace ERROR: Command failed: . Previously a missing audio file, an out-of-range ayah, a bad surah number, and an already-stopped pause all collapsed to the same empty error — the actual reason lived only in ~/.quran-player/control/daemon.log. Concrete example: a quran-daemon play on a machine without 076013.mp3 used to print ERROR: Command failed:; it now prints ERROR: Audio file not found: 076013.mp3 in /home/…/audio (use 'quran-daemon dir ' to change).

•Daemon.error_msg is per-thread. It was a single instance attribute; two concurrent clients could read each other's error text, and one client could inherit a stale message left behind by the main loop's handle_playback_end() failure. Now backed by threading.local(), and cleared at the start of every dispatch.

•Auto-advance failures are visible. handle_playback_end() now returns bool; when the next verse has no audio (a very common state for a partial recitation set), the main loop logs a distinct WARNING|Auto-advance stopped at S:A line in addition to the existing ERROR|Audio file not found line, so the reason the player went quiet is visible without grepping the log by hand.

•config.get() treats empty strings as "use the documented default". Uncommenting files_directory = used to resolve to "" rather than the bundled sample directory, so every audio lookup became a relative path and silently failed. files_directory = now resolves to ~/.config/quran-player/sample/; executable = still resolves to "" (its documented default).

•playback_controller no longer hard-imports pygame. The mixin called pygame.mixer.music.get_busy() and caught pygame.error directly, forcing the audio dependency into every code path — including stop, status, and info against a broken venv. Moved to a new AudioPlayer.is_busy() method; neither daemon.py nor the mixin needs pygame at import time. Side effect: if the mixer errors during a busy check, is_busy() returns None and the daemon reinitialises rather than silently treating the hiccup as "playback ended".

•audio_player.init_audio() no longer passes allowedchanges=0. That flag refused to open any device whose native format differed from 44100 Hz / 16-bit / stereo — common on USB headsets and HDMI outputs, and the reason some users saw Audio init failed: No available audio device on hardware that worked fine elsewhere. Restored SDL's default (frequency and channel changes allowed).

•ns / ps clear repeat mode. They used to *widen* the repeat range to cover the new surah, which was inconsistent with load (which clears it) and almost never what the user wanted. Behavior now matches load. repeat_off is also idempotent — returns OK when repeat is already off, instead of ERROR: Command failed:.

•config_set applies Conky enable changes immediately. Changing [conky] enable via config_set used to update config.ini but leave the running Conky process untouched until daemon restart. conky_config_set already did the right thing. config_set now starts or stops Conky to match the new setting before returning OK.

•reload_config is reachable from the CLI. The command was registered in Daemon.valid_commands but had no subparser in cli_parser.py, so quran-daemon reload_config was unreachable — typing it hit the generic "Unknown command" path. Added; re-reads config.ini from disk and refreshes the daemon's config-derived state (audio_base, conky_enabled).

•Documentation rewritten to match the code.

•manual.txt regenerated to 2.1.0: removed the entire [image] section (deleted in 2.0.0) and the PYTHON_IMAGE_* environment variables (never read anywhere). Added the Conky display commands, reload_config, and the config_* / conky_config_* command groups. Documented the actual [daemon] / [conky] schema, plus the separate JSON conky_config.json file for fine-grained Conky display settings.

•quran-daemon.1 regenerated to match — same removals, same additions, release date brought current.

•bump_version.py's regex for manual.txt required ^Version: v… at column zero, but the real file has leading whitespace, so the script silently skipped manual.txt on every bump. That is why it was frozen at v1.3.0 since the 1.x days. Regex now allows leading whitespace.

•README.md: corrected the "zero runtime dependencies" claim — the app needs pygame-ce, PyQt5, portalocker, arabic-reshaper, and python-bidi in addition to the system-level conky and pactl — and removed references to reshape_arabic.sh and move_dead_files.sh, neither of which exist in the repo.

•install.py no longer deletes user files from audio/. copy_tree_incremental swept destination files not present in the source, which is correct for quran-text/ (installer-managed) but not for audio/, where users drop additional recitations if they point files_directory at it. copy_tree_incremental gained a remove_orphans=True kwarg; audio/ is now add/update-only, while quran-text/ still prunes orphans.

•log responses capped at 10 000 lines. Prevents a runaway log from exceeding the client's 64 KB single-recv buffer and being silently truncated. Client-side recv loops (daemon._recv_all and DaemonCommunicator.send_command) also loop until EOF rather than making one recv(65536) call, so log, config_get, and info responses larger than 64 KB arrive intact.

•about() skips OSC-8 hyperlinks outside a TTY. Under tmux, screen, or a non-TTY pipe, about used to emit literal \e]8;;…\a escape sequences around the "Website" and "GitHub Repository" lines. Now downgrades to plain label: url when TERM is dumb or unset, or when stdout is not a terminal.

•conky_manager._reshape_verse() no longer mutates sys.path. Loads reshape_arabic.py via importlib.util.spec_from_file_location and caches the callable on the instance. No process-wide import-path pollution, no risk of a stale reshape_arabic module being picked up on a subsequent import.

•main() no longer instantiates a full Daemon for client commands. stop, play, pause, toggle, next, prev, ns, ps, goto, load, repeat, dir, status, log, every conky_*, and every config_* / conky_config_* command now talk to the socket directly without loading SURAH_AYAT, playback state, or log-rotation settings. start, info (when the daemon is down), and config (regenerate) still construct one, as they should. Side effect: a corrupt playback_state.ini no longer produces log churn on every CLI command.

•quran_gui.DaemonCommunicator gained a 5 s socket timeout. send_command had no timeout, so a wedged daemon froze the Qt event loop the next time the 2 s / 7 s status timer fired. is_running() was already guarded; send_command now matches.

•Cleanup.

•Removed quran_daemon_conky/screenshot-2025-07-12--23-28-48.png (dev artifact, never installed) and added *.png / !icon.png to .gitignore.

•Removed the unused self.repeat_range tuple on QuranPlayer (each consumer already read the live status JSON).

•quran_search.read_uthmani and read_simplified gained the same file-existence guards read_chapters already had.

•Normalised the 4-vs-5-space indentation mismatch in cli_parser.py's config_set block and dropped the config_set "$(cat my_config.ini)" example from --help (breaks on any config containing quotes; the stdin form is documented and correct).

•Refreshed the stale v1.1.0 docstring at the top of quran_gui.py.

Compatibility notes

•Wire protocol for config_set / conky_config_set changed. New clients send config_set\n followed by shutdown(SHUT_WR); the daemon reads until EOF. An old client (pre-2.1.0 quran-gui) sending the inline form config_set \n still works — the daemon accumulates the payload through the same read buffer and applies it correctly — but the daemon will idle on the socket for the full 5 s idle timeout before responding, because it never sees a SHUT_WR that the old client didn't send. Update both sides together to avoid the delay.

•Error message format changed. Callers that pattern-match the literal ERROR: Command failed: will now see ERROR: in its place. OK responses are unchanged. status, log, info, about, and the raw-text commands (config_get, config_defaults, conky_config, conky_config_defaults) are unchanged.

•ns / ps behavior changed. They now clear repeat mode (matching load) instead of widening the repeat range to cover the new surah. If any local script depended on the widening, re-issue repeat after ns/ps.

•No migration needed. No config keys were added, renamed, or removed. Existing config.ini and conky_config.json files are read unchanged. python install.py is sufficient to pick up the new daemon.py and quran_gui.py; --clean is not required, and user-added files in ~/.quran-player/audio/ are now preserved (that itself is a change — see the install.py note above).

2.0.3 — 2026-10-23

•Daemon split out of daemon.py into focused modules. The 1,200-line monolith is now Daemon (lifecycle + socket client protocol) plus mixins: logging_utils (log_action + rotation), state_manager (playback state save/load + verse navigation), conky_manager (conky process lifecycle + verse file writes), playback_controller (play/pause/load/repeat/dir), status_reports (status/log/config/info + about). Shared constants live in quran_data.SURAH_AYAT; is_daemon_running and cleanup_orphaned_files moved to daemon_utils; argparse setup moved to cli_parser.build_parser(). No behavior change — every method body is byte-identical, every call site still goes through self.().

•Orphan conky processes are now reliably killed. _stop_conky no longer bails early when self.conky_process is None or stale — it always sweeps for conky processes using our config. _start_conky sweeps before starting instead of trusting the tracked handle. _find_conky_pids matches by process *name* (pgrep -x conky) and verifies via /proc//cmdline rather than a cmdline regex, so unrelated processes can't be hit and a manually-started conky with our config *is* hit. Guards against PID reuse by clearing the tracked handle before the sweep.

•info now reports conky PIDs and single-instance status. The Conky Display block gained PIDs and Single inst. lines, populated from _find_conky_pids(). Reading them alongside Running makes the orphan bug self-diagnosing: Running: Yes + PIDs: [a, b] means an orphan is present and needs conky_off && conky_on.

•handle_conky_off verifies the result. Returns ERROR (with the surviving PID list) when conky is still alive after the sweep, instead of reporting OK on a lie. This was the visible symptom that drove the orphan-sweep rewrite — see 2.0.1's conky_off fix, which hardened the kill path but not the verify path.

•Amiri font is bundled. quran_daemon_conky/Amiri-Regular.ttf is installed to ~/.local/share/fonts/ on fresh installs where fc-match Amiri doesn't already resolve (from distro package or previous install). fc-cache -f is invoked to refresh the cache. The destination is recorded in ~/.quran-player/.installed-fonts so --uninstall removes exactly what the installer added — user-installed fonts are never touched. On a system with fonts-hosny-amiri installed the bundle is skipped. Diagnostic line added to the install summary: Amiri font : bundled → … | system (fc-match resolved) | ⚠ not installed.

•Installer sweeps obsolete files in ~/.quran-player/conky/. install_conky now removes anything not in CONKY_FILES on every incremental install. Prevents stale helpers from previous versions (e.g. reshape_arabic.sh, removed in 2.0.2) lingering indefinitely. Matches the pattern already used by copy_tree_incremental for quran-text/ and audio/.

•Uninstall leaves the system clean. --uninstall now:

•kills orphan conky processes even when the daemon isn't running (kill_orphan_conky, previously only reachable via stop_daemon_if_running if the daemon was alive),

•removes bundled fonts via the .installed-fonts marker before rmtree destroys it,

•deletes /tmp/quran_verse.txt, /tmp/quran_verse_reshaped.txt, and any stray /tmp/.quran_verse_* temp files from crashed writes,

•calls update-desktop-database after removing the .desktop file so launchers stop showing quran-gui until reboot.

•New daemon modules added to APP_FILES so incremental install picks them up: daemon_utils.py, cli_parser.py, logging_utils.py, state_manager.py, conky_manager.py, playback_controller.py, status_reports.py, quran_data.py. Existing 2.0.2 installs must be re-run with python install.py — running a post-split daemon.py against the pre-split ~/.quran-player/ fails with ModuleNotFoundError.

2.0.2 — 2026-10-23

•Conky verse display rewritten: daemon pre-reshapes Arabic per verse and writes /tmp/quran_verse_reshaped.txt atomically via tempfile + os.replace, eliminating the empty-file / partial-read flicker on verse transitions.

•conky_quran template collapsed from 6 ${execi} rows to one ${execpi} calling display_verse.sh — one script invocation per refresh instead of six, so all rows always reflect the same verse (no mixed-row frames), and empty lines no longer occupy blank rows.

•display_verse.sh normalizes U+2028/U+2029/CR to plain \n before splitting — tac/read/head don't recognize those separators, which previously caused the whole verse to render as one ${alignr} row.

•reshape_arabic.py made importable (sys.argv handling moved under __main__); daemon imports its reshape_and_wrap_arabic_text directly.

•reshape_arabic.sh removed (was the per-line Python-spawn wrapper).

2.0.1 — 2026-10-23

•conky_off verifies the sweep result and returns ERROR (with surviving PIDs) instead of OK when conky survives — previously the daemon reported success whether or not the kill had actually worked.

•Conky detection hardened: matches by process name (pgrep -x conky) and verifies the match by reading /proc//cmdline, so unrelated processes can't be hit and manually-started conky with our config *is* hit. Guards against PID reuse by clearing the tracked handle before the sweep.

•handle_conky_off now verifies the result and returns ERROR (with the surviving PID) if conky is still alive, instead of reporting false success.

•Repeat mode persists across daemon restarts. playback_state.ini now stores repeat, repeat_start, repeat_end; load_playback_state restores and validates the range against surah_ayat (bad ranges are dropped with a log entry). handle_repeat_off writes the state. qd stop → qd toggle no longer loses repeat.

•handle_stop flushes playback state to disk before exit, so verse + repeat are current even if the process is killed shortly after.

•All CLI subcommands now carry description + epilog (usage notes and examples) and use RawDescriptionHelpFormatter, so qd repeat -h, qd load -h, qd goto -h, etc. actually print something useful. Argument help strings added for goto, load, repeat, and dir.

2.0.0 — 2026-10-20

•Linux-native, Conky-only. Dropped Windows/macOS support, removed feh and the PyQt viewer.

•Conky desktop verse display — daemon-managed lifecycle (start/stop/toggle with the daemon).

•New commands: conky_on, conky_off, conky_toggle.

•Crash-safe: orphaned conky processes are reaped on next start/stop.

•qd stop now guarantees conky is gone before returning.

•One-command installer: python install.py (no root, no venv management required from user).

•Replaced pillow with arabic-reshaper + python-bidi (needed by the Conky shaper).

•Socket permissions hardened to 0600; daemon lock acquired atomically before PID write.

•info command now queries the live daemon state over the socket (was reading stale state).

1.3.0 — 2026-10-18

•Cross-platform daemon (Linux/macOS/Windows)

•feh-based verse image display with PyQt fallback

•Initial public source release

Visits: 0

Leave a comment

5000 characters max.