-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·426 lines (386 loc) · 18.2 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·426 lines (386 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#!/bin/bash
# install.sh — user-local install for Sidemark
# Usage: ./install.sh install
# ./install.sh -y install, auto-confirm dependency prompt
# ./install.sh --walker-menu also install the walker/elephant recent-files menu
# ./install.sh --register-pptx also register as default handler for PowerPoint files
# ./install.sh --uninstall remove everything
set -euo pipefail
_usage() {
cat <<'EOF'
install.sh — user-local install for Sidemark
Usage:
./install.sh [OPTIONS]
Options:
-h, --help Show this help message and exit.
-y, --yes Auto-confirm the missing-dependency install prompt.
--with-ocr Also install OCR support (ocrmypdf + an English language
pack) for scanned PDFs, without prompting.
--walker-menu Also install the walker/elephant recent-files launcher menu.
--register-pptx
Also register Sidemark as the default handler for PowerPoint
files (PDF and Markdown are always registered).
--uninstall Remove Sidemark (binary, desktop entry, icons, completion).
When OCR support isn't already present and --with-ocr is not given, an
interactive install prompts whether to add it (defaults to no).
Examples:
./install.sh
./install.sh --with-ocr
./install.sh -y --walker-menu
./install.sh --uninstall
EOF
}
_YES=0
_WALKER=0
_PPTX=0
_OCR=0
for _arg in "$@"; do
case "$_arg" in
-h|--help) _usage; exit 0 ;;
-y|--yes) _YES=1 ;;
--with-ocr) _OCR=1 ;;
--walker-menu) _WALKER=1 ;;
--register-pptx) _PPTX=1 ;;
--uninstall) ;; # handled below
-*) echo "Unknown option: $_arg" >&2; _usage >&2; exit 2 ;;
esac
done
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_DIR="$HOME/.local/share/sidemark"
BIN_DIR="$HOME/.local/bin"
DESKTOP_DIR="$HOME/.local/share/applications"
ICON_BASE="$HOME/.local/share/icons/hicolor"
COMP_DIR="$HOME/.local/share/bash-completion/completions"
DESKTOP_ID="de.hspitz.sidemark"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
ok() { echo -e " ${GREEN}✓${NC} $1"; }
warn() { echo -e " ${YELLOW}!${NC} $1"; }
fail() { echo -e " ${RED}✗${NC} $1" >&2; exit 1; }
step() { echo -e "\n${BOLD}$1${NC}"; }
# ── uninstall ──────────────────────────────────────────────────────────────────
if [[ "${1:-}" == "--uninstall" ]]; then
step "Uninstalling Sidemark…"
rm -rf "$INSTALL_DIR"
rm -f "$BIN_DIR/sidemark"
rm -f "$DESKTOP_DIR/$DESKTOP_ID.desktop"
rm -f "$HOME/.config/elephant/menus/sidemark_recent.lua"
rm -f "$COMP_DIR/sidemark"
rm -f "$ICON_BASE/scalable/apps/$DESKTOP_ID.svg"
for size in 16 32 48 64 128 256; do
rm -f "$ICON_BASE/${size}x${size}/apps/$DESKTOP_ID.png"
done
gtk-update-icon-cache -f -t "$ICON_BASE" 2>/dev/null || true
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
ok "Uninstalled."
exit 0
fi
# ── distro detection ──────────────────────────────────────────────────────────
_DISTRO="unknown"
if command -v pacman &>/dev/null; then _DISTRO="arch"
elif command -v apt &>/dev/null; then _DISTRO="deb"
elif command -v dnf &>/dev/null; then _DISTRO="rpm"
fi
# Print the right install hint for the detected distro.
# Args: arch-hint deb-hint rpm-hint
_hint() {
case "$_DISTRO" in
arch) echo "sudo pacman -S $1" ;;
deb) echo "sudo apt install $2" ;;
rpm) echo "sudo dnf install $3" ;;
*) echo "install: arch=$1 deb=$2 rpm=$3" ;;
esac
}
# ── dependency check ───────────────────────────────────────────────────────────
step "Checking dependencies…"
_MISS_ARCH=(); _MISS_DEB=(); _MISS_RPM=(); _MISS_PIP=()
# _need ARCH_PKGS DEB_PKGS RPM_PKGS [pip=PKG]
_need() {
local arch="$1" deb="$2" rpm="$3" pip="${4:-}"
local display="${arch:-${pip}}"
read -ra _a <<< "$arch"; _MISS_ARCH+=("${_a[@]}")
read -ra _d <<< "$deb"; _MISS_DEB+=("${_d[@]}")
read -ra _r <<< "$rpm"; _MISS_RPM+=("${_r[@]}")
[[ -n "$pip" ]] && _MISS_PIP+=("$pip")
echo -e " ${RED}✗${NC} Missing: $display"
}
# check_py TEST ARCH_PKGS DEB_PKGS RPM_PKGS [pip=PKG]
check_py() {
/usr/bin/python3 -c "$1" 2>/dev/null || _need "$2" "$3" "$4" "${5:-}"
}
if ! command -v python3 >/dev/null 2>&1; then
fail "python3 not found → $(_hint python python3 python3)"
fi
check_py "import gi" \
"python-gobject" "python3-gi python3-gi-cairo" "python3-gobject"
check_py "import gi; gi.require_version('Gtk','4.0'); from gi.repository import Gtk" \
"gtk4" "gir1.2-gtk-4.0 libgtk-4-1" "gtk4"
check_py "import gi; gi.require_version('Adw','1'); from gi.repository import Adw" \
"libadwaita" "gir1.2-adw-1 libadwaita-1-0" "libadwaita"
# PyMuPDF: Arch = pacman, others = pip
if ! /usr/bin/python3 -c "import fitz" 2>/dev/null; then
case "$_DISTRO" in
arch) _need "python-pymupdf" "" "" ;;
*) _need "" "" "" "pymupdf" ;;
esac
fi
check_py "import numpy" \
"python-numpy" "python3-numpy" "python3-numpy"
check_py "import cairo" \
"python-cairo" "python3-gi-cairo" "python3-cairo"
check_py "import gi; gi.require_version('GtkSource','5'); from gi.repository import GtkSource" \
"gtksourceview5" "gir1.2-gtksource-5 libgtksourceview-5-0" "gtksourceview5"
if ! find /usr/share/icons/Adwaita -name "go-next-symbolic*" 2>/dev/null | grep -q .; then
warn "adwaita-icon-theme not found — icons may be missing."
_need "adwaita-icon-theme" "adwaita-icon-theme" "adwaita-icon-theme"
fi
# DejaVu Sans is what the PDF export embeds so notes keep their maths: the
# built-in PDF fonts are Latin-1, and without it every α, ∑, ℝ and → in an
# exported handout becomes a question mark. Warned about rather than fatal —
# the export still runs — but it is the kind of missing that only shows up
# after you have handed the file to somebody.
if ! ls /usr/share/fonts/TTF/DejaVuSans.ttf \
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf \
/usr/share/fonts/dejavu/DejaVuSans.ttf \
/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf >/dev/null 2>&1; then
warn "DejaVu Sans not found — maths in exported notes will export as '?'."
_need "ttf-dejavu" "fonts-dejavu-core" "dejavu-sans-fonts"
fi
# ── auto-install missing packages ─────────────────────────────────────────────
_has_missing() {
[[ ${#_MISS_ARCH[@]} -gt 0 || ${#_MISS_DEB[@]} -gt 0 || \
${#_MISS_RPM[@]} -gt 0 || ${#_MISS_PIP[@]} -gt 0 ]]
}
if _has_missing; then
echo ""
if [[ $_YES -eq 1 || ! -t 0 ]]; then # -y, or no terminal to prompt on (CI, curl|bash)
_ans="Y"
else
read -rp " Install missing packages automatically? [Y/n] " _ans
fi
if [[ "${_ans:-Y}" =~ ^[Yy]$ ]]; then
case "$_DISTRO" in
arch)
[[ ${#_MISS_ARCH[@]} -gt 0 ]] && sudo pacman -S --needed --noconfirm "${_MISS_ARCH[@]}"
;;
deb)
[[ ${#_MISS_DEB[@]} -gt 0 ]] && sudo apt-get install -y "${_MISS_DEB[@]}"
if [[ ${#_MISS_PIP[@]} -gt 0 ]]; then
/usr/bin/python3 -m pip --version &>/dev/null || sudo apt-get install -y python3-pip
/usr/bin/python3 -m pip install --user --break-system-packages "${_MISS_PIP[@]}"
fi
;;
rpm)
[[ ${#_MISS_RPM[@]} -gt 0 ]] && sudo dnf install -y "${_MISS_RPM[@]}"
if [[ ${#_MISS_PIP[@]} -gt 0 ]]; then
/usr/bin/python3 -m pip --version &>/dev/null || sudo dnf install -y python3-pip
/usr/bin/python3 -m pip install --user --break-system-packages "${_MISS_PIP[@]}"
fi
;;
*)
[[ ${#_MISS_PIP[@]} -gt 0 ]] && /usr/bin/python3 -m pip install --user --break-system-packages "${_MISS_PIP[@]}"
;;
esac
# Re-verify after install
step "Re-checking dependencies…"
_MISS_ARCH=(); _MISS_DEB=(); _MISS_RPM=(); _MISS_PIP=()
check_py "import gi" \
"python-gobject" "python3-gi python3-gi-cairo" "python3-gobject"
check_py "import gi; gi.require_version('Gtk','4.0'); from gi.repository import Gtk" \
"gtk4" "gir1.2-gtk-4.0 libgtk-4-1" "gtk4"
check_py "import gi; gi.require_version('Adw','1'); from gi.repository import Adw" \
"libadwaita" "gir1.2-adw-1 libadwaita-1-0" "libadwaita"
if ! /usr/bin/python3 -c "import fitz" 2>/dev/null; then
case "$_DISTRO" in
arch) _need "python-pymupdf" "" "" ;;
*) _need "" "" "" "pymupdf" ;;
esac
fi
check_py "import numpy" \
"python-numpy" "python3-numpy" "python3-numpy"
check_py "import cairo" \
"python-cairo" "python3-gi-cairo" "python3-cairo"
check_py "import gi; gi.require_version('GtkSource','5'); from gi.repository import GtkSource" \
"gtksourceview5" "gir1.2-gtksource-5 libgtksourceview-5-0" "gtksourceview5"
_has_missing && fail "Some dependencies still missing after install."
else
fail "Aborted — install missing packages first."
fi
fi
ok "All required dependencies present."
# ── optional: OCR support (ocrmypdf) ───────────────────────────────────────────
# ocrmypdf adds a searchable text layer to scanned PDFs. It's heavy (Tesseract +
# Ghostscript) so it's never installed silently — only via --with-ocr or an
# interactive prompt, and only when it isn't already present.
_install_ocr() {
case "$_DISTRO" in
arch)
# ocrmypdf lives in the AUR on Arch; tesseract-data-eng is official.
local helper=""
command -v paru >/dev/null 2>&1 && helper="paru"
[[ -z "$helper" ]] && command -v yay >/dev/null 2>&1 && helper="yay"
if [[ -n "$helper" ]]; then
# AUR helpers call sudo themselves — never run them as root.
# Put the system python first: a version manager (mise/pyenv/
# asdf) shim shadowing /usr/bin/python breaks makepkg's
# `python -m build` when building AUR Python deps (e.g. fpdf2).
PATH="/usr/bin:$PATH" "$helper" -S --needed --noconfirm \
ocrmypdf tesseract-data-eng
else
warn "No AUR helper (paru/yay) found — installing the engine from the"
warn "official repos and ocrmypdf itself via pip."
sudo pacman -S --needed --noconfirm tesseract tesseract-data-eng ghostscript
/usr/bin/python3 -m pip install --user --break-system-packages ocrmypdf
fi
;;
deb) sudo apt-get install -y ocrmypdf ;; # Debian's ocrmypdf pulls eng
rpm) sudo dnf install -y ocrmypdf tesseract-langpack-eng ;;
*) warn "Don't know how to install ocrmypdf on this system — install it manually."; return 1 ;;
esac
}
step "Optional: OCR for scanned PDFs…"
if command -v ocrmypdf >/dev/null 2>&1; then
ok "OCR support (ocrmypdf) already installed."
else
_DO_OCR=0
if [[ $_OCR -eq 1 ]]; then
_DO_OCR=1
elif [[ $_YES -eq 1 || ! -t 0 ]]; then # -y, or no terminal to prompt on (CI, curl|bash)
warn "OCR support (ocrmypdf) not installed — re-run with --with-ocr to add it."
else
read -rp " Install optional OCR support (ocrmypdf, for scanned PDFs)? [y/N] " _ans
[[ "${_ans:-N}" =~ ^[Yy]$ ]] && _DO_OCR=1
fi
if [[ $_DO_OCR -eq 1 ]]; then
if _install_ocr && { command -v ocrmypdf >/dev/null 2>&1 || [[ -x "$HOME/.local/bin/ocrmypdf" ]]; }; then
ok "OCR support installed."
else
warn "Could not install ocrmypdf — Sidemark still runs; OCR just stays unavailable."
if [[ "$_DISTRO" == "arch" ]]; then
warn "Common causes on Arch, then retry ./install.sh --with-ocr :"
warn " • stale package DB / 404s → sudo pacman -Syu"
warn " • a Python version manager (mise/pyenv/asdf) shadowing the"
warn " system python during the AUR build → build in a shell where"
warn " /usr/bin/python comes first (e.g. 'mise deactivate')"
fi
fi
else
ok "Skipping OCR support."
fi
fi
if command -v rsvg-convert >/dev/null 2>&1; then
HAVE_RSVG=1; ok "rsvg-convert found — will render PNG icons."
else
HAVE_RSVG=0; warn "rsvg-convert not found — only SVG icon will be installed."
fi
# ── install ────────────────────────────────────────────────────────────────────
step "Installing…"
mkdir -p "$INSTALL_DIR" "$BIN_DIR" "$DESKTOP_DIR"
mkdir -p "$ICON_BASE/scalable/apps"
for size in 16 32 48 64 128 256; do
mkdir -p "$ICON_BASE/${size}x${size}/apps"
done
# Main script
install -m 755 "$SCRIPT_DIR/sidemark.py" "$INSTALL_DIR/sidemark.py"
ok "sidemark.py → $INSTALL_DIR/"
# Stamp what this copy IS. An installed copy has no .git, so `sidemark
# --version` can only report its commit — and whether the tree it came from was
# clean — if that is recorded now. It is also the only moment the dirtiness is
# still knowable.
_commit=""; _dirty=""
if command -v git >/dev/null && git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
_commit=$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || true)
_dirty=$(git -C "$SCRIPT_DIR" status --porcelain 2>/dev/null | grep -c . || true)
fi
# ${x:+..}${x:-..} is NOT an if/else — when x is set BOTH expand, which wrote
# `"abc123"abc123` and made the file unparseable. Build the JSON values first.
if [ -n "$_commit" ]; then _commit_json="\"$_commit\""; else _commit_json=null; fi
if [ -n "$_dirty" ]; then _dirty_json="$_dirty"; else _dirty_json=null; fi
cat > "$INSTALL_DIR/build.json" <<JSON
{
"commit": $_commit_json,
"dirty": $_dirty_json,
"built": "$(date '+%Y-%m-%d %H:%M')",
"by": "install.sh"
}
JSON
chmod 644 "$INSTALL_DIR/build.json"
ok "build.json → $INSTALL_DIR/ (what \`sidemark --version\` reports)"
# The browser port, served to a phone by Share to phone. Not optional any
# more: scanning the QR lands on it, and without it a share falls back to the
# small image viewer that cannot zoom. `test/` and package.json are
# development-only, exactly as the Pages workflow leaves them out.
if [ -d "$SCRIPT_DIR/web" ]; then
rm -rf "$INSTALL_DIR/web"
mkdir -p "$INSTALL_DIR/web"
(cd "$SCRIPT_DIR/web" && tar --exclude=test --exclude=node_modules \
--exclude=dist --exclude=package.json --exclude=package-lock.json \
-cf - .) | (cd "$INSTALL_DIR/web" && tar -xf -)
ok "web/ → $INSTALL_DIR/web/ (phone editor)"
fi
# Wrapper so 'sidemark' works from any shell / Exec line
cat > "$BIN_DIR/sidemark" <<EOF
#!/bin/sh
exec /usr/bin/python3 "$INSTALL_DIR/sidemark.py" "\$@"
EOF
chmod 755 "$BIN_DIR/sidemark"
ok "wrapper → $BIN_DIR/sidemark"
# Bash completion for the 'sidemark' command (tab-completes options and files)
mkdir -p "$COMP_DIR"
install -m 644 "$SCRIPT_DIR/extras/sidemark.bash" "$COMP_DIR/sidemark"
ok "bash completion → $COMP_DIR/sidemark"
# Desktop entry
install -m 644 "$SCRIPT_DIR/de.hspitz.sidemark.desktop" \
"$DESKTOP_DIR/$DESKTOP_ID.desktop"
ok ".desktop file → $DESKTOP_DIR/"
# Default handler
xdg-mime default "$DESKTOP_ID.desktop" application/pdf 2>/dev/null || true
xdg-mime default "$DESKTOP_ID.desktop" text/markdown 2>/dev/null || true
xdg-mime default "$DESKTOP_ID.desktop" text/x-markdown 2>/dev/null || true
ok "registered as default for PDF and Markdown."
# PowerPoint opens via LibreOffice conversion — opt-in, since most users want
# an office suite as their .pptx default.
if [[ $_PPTX -eq 1 ]]; then
xdg-mime default "$DESKTOP_ID.desktop" application/vnd.ms-powerpoint 2>/dev/null || true
xdg-mime default "$DESKTOP_ID.desktop" \
application/vnd.openxmlformats-officedocument.presentationml.presentation 2>/dev/null || true
ok "registered as default for PowerPoint."
fi
# Icons
install -m 644 "$SCRIPT_DIR/icon.svg" \
"$ICON_BASE/scalable/apps/$DESKTOP_ID.svg"
if [[ $HAVE_RSVG -eq 1 ]]; then
for size in 16 32 48 64 128 256; do
rsvg-convert "$SCRIPT_DIR/icon.svg" -w "$size" -h "$size" \
-o "$ICON_BASE/${size}x${size}/apps/$DESKTOP_ID.png"
done
ok "icons (SVG + PNG) → $ICON_BASE/"
else
ok "icon (SVG only) → $ICON_BASE/"
fi
# Walker/elephant launcher menu (Omarchy): recent files in the launcher.
# Opt-in via --walker-menu — not every user wants entries in their launcher.
if [[ $_WALKER -eq 1 ]]; then
ELEPHANT_MENUS="$HOME/.config/elephant/menus"
if [[ -d "$ELEPHANT_MENUS" ]] && command -v jq &>/dev/null; then
install -m 644 "$SCRIPT_DIR/extras/sidemark_recent.lua" \
"$ELEPHANT_MENUS/sidemark_recent.lua"
systemctl --user try-restart elephant 2>/dev/null || true
ok "walker menu → $ELEPHANT_MENUS/sidemark_recent.lua"
else
warn "walker menu skipped: needs ~/.config/elephant/menus and jq"
fi
fi
# Refresh caches
gtk-update-icon-cache -f -t "$ICON_BASE" 2>/dev/null || true
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
ok "icon and desktop caches refreshed."
# PATH hint
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
warn "$HOME/.local/bin is not in your PATH."
warn "Add to ~/.bashrc or ~/.zshrc: export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
echo -e "\n${GREEN}${BOLD}Done!${NC}"
echo " Launch: sidemark [file.pdf]"
echo " Help: sidemark --help"
echo " Uninstall: ./install.sh --uninstall"
echo " (start a new shell to pick up 'sidemark' tab-completion)"