#!/usr/bin/env bash # Dikte installer: dependency check, launchers, global shortcuts. set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Nothing below this line is true on a Mac: no XDG directories, no .desktop # files, no shortcut registry, and an application is a bundle rather than a # path. That is a second script rather than a branch through this one, and # update.sh reaches it through here without having to know which it is on. if [[ "$(uname -s)" == "Darwin" ]]; then exec "$DIR/install-mac.sh" "$@" fi PY="$(command -v python3)" BIN_DIR="$HOME/.local/bin" APP_DIR="$HOME/.local/share/applications" AUTOSTART_DIR="$HOME/.config/autostart" SHORTCUT="${1:-Ctrl+Space}" # Without the colon, so that a second argument given as "" stays empty. That is # how update.sh says "this one was turned off", as against not saying anything. CANCEL_SHORTCUT="${2-Ctrl+Alt+Space}" say() { printf ' %s\n' "$1"; } ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; } warn() { printf ' \033[33m!\033[0m %s\n' "$1"; } echo echo "Installing Dikte" echo "────────────────" # 1. Dependencies ---------------------------------------------------------- missing=() audio_cmds=(ffmpeg) if command -v parec >/dev/null || command -v pw-record >/dev/null; then : else missing+=("pulseaudio-utils-or-pipewire-audio") fi if [[ "${XDG_SESSION_TYPE:-}" == "x11" ]]; then desktop_cmds=(xclip xdotool) else desktop_cmds=(wl-copy wl-paste ydotool) fi for cmd in "${audio_cmds[@]}" "${desktop_cmds[@]}"; do command -v "$cmd" >/dev/null || missing+=("$cmd") done python3 -c 'import PyQt6.QtWidgets' 2>/dev/null || missing+=("python-pyqt6") if ((${#missing[@]})); then warn "Missing: ${missing[*]}" say "Ubuntu X11: sudo apt install pulseaudio-utils xclip xdotool ffmpeg" say "Arch Wayland: sudo pacman -S --needed pipewire-audio wl-clipboard ydotool ffmpeg python-pyqt6" say "Fedora Wayland: sudo dnf install pipewire-utils wl-clipboard ydotool ffmpeg-free python3-pyqt6" echo else ok "All dependencies present" fi # 2. ydotoold -------------------------------------------------------------- # What auto-paste needs is a socket it may write to, which is not the same # question as whether the unit is up: Fedora ships ydotool as a system service # only, and its socket stays root-owned at mode 600, so there the daemon can be # running while every paste is refused. The socket file outlives the daemon, # though, so the process has to be there as well for the answer to be yes. if [[ "${XDG_SESSION_TYPE:-}" != "x11" ]] && command -v ydotool >/dev/null; then socket="${YDOTOOL_SOCKET:-${XDG_RUNTIME_DIR:-/tmp}/.ydotool_socket}" alive() { pgrep -x ydotoold >/dev/null 2>&1; } if [[ -w "$socket" ]] && alive; then ok "ydotoold is running (auto-paste ready)" elif systemctl is-active --quiet ydotool 2>/dev/null; then warn "ydotoold's socket is not yours to write to, so auto-paste will fail" say "Hand it over with the drop-in in the README's Fedora section." elif alive; then warn "ydotoold is running, but it did not put its socket at $socket" say "Point Dikte at the one it did make: export YDOTOOL_SOCKET=..." else warn "ydotoold is not running, auto-paste will not work" say "systemctl --user enable --now ydotool (on Fedora: see the README)" fi fi # 3. Launchers ------------------------------------------------------------- mkdir -p "$BIN_DIR" "$APP_DIR" "$AUTOSTART_DIR" ln -sf "$DIR/dikte.py" "$BIN_DIR/dikte" chmod +x "$DIR/dikte.py" ok "Command installed: $BIN_DIR/dikte" case ":$PATH:" in *":$BIN_DIR:"*) ;; *) warn "$BIN_DIR is not on your PATH. For fish: fish_add_path $BIN_DIR" ;; esac cat > "$APP_DIR/dikte.desktop" < "$AUTOSTART_DIR/dikte.desktop" <&1)"; then ok "$3: $2" else # One line: the rest of what it has to say about KWin is printed below. warn "${out%%$'\n'*}" fi } if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null; then register toggle "$SHORTCUT" "Start and stop" if [[ -n "$CANCEL_SHORTCUT" ]]; then register cancel "$CANCEL_SHORTCUT" "Discard the recording" fi # Which of the three mechanisms this session got is Dikte's answer to give, # not this script's. Guessing from XDG_CURRENT_DESKTOP here is how every # session that was neither GNOME nor KDE used to be promised a KWin that was # never running. case "$("$PY" -c 'import sys; sys.path.insert(0, sys.argv[1]); import hotkey; print(hotkey.backend())' "$DIR" 2>/dev/null)" in kde) warn "KWin only reads these at startup, so they go live after your next" say "login. Until then open Settings → Shortcuts and turn on the" say "built-in listener to use them right away." ;; gnome) ;; *) if id -nG 2>/dev/null | tr ' ' '\n' | grep -qx input; then say "Your desktop keeps no shortcut registry, so Dikte listens for these" say "keys itself while it is running." else warn "Your desktop keeps no shortcut registry, so Dikte listens for these" say "keys itself, and it cannot read /dev/input yet:" say " sudo usermod -aG input $(id -un) (then log out and back in)" fi ;; esac else warn "PyQt6 is missing, so no shortcut was registered. Install it, then run:" say "dikte shortcut install toggle --combo '$SHORTCUT'" fi echo ok "Done. Start it with: dikte" say "The settings window opens on first run: download a speech model, or add" say "an OpenAI or OpenRouter key instead." echo