fix(uninstall): remove IBus data dir & stop app by PID file not pgrep -f (#597)
* fix(uninstall): remove IBus runtime data directory on uninstall Discussion #559 reported ~/.local/share/vocalinux-ibus surviving uninstall. PR #569 fixed launcher wrappers but not this IBus IPC directory (socket, PID file, stderr log). Stop the engine via engine.pid when present, remove the directory regardless of --keep-data, and verify it in post-checks. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * fix(scripts): stop Vocalinux by PID file, not pgrep -f pgrep -f vocalinux matches any process whose argv mentions the repo path (editors, shells, test runners) and can kill unrelated local work. AGENTS.md already warns against this. - Prefer PIDs from instance.lock and vocalinux-ibus/engine.pid - Verify each candidate via is_vocalinux_process (python -m vocalinux.main, ibus_engine.py under vocalinux, or argv0 basename vocalinux/vocalinux-gui) - Narrow pgrep fallback only for those entrypoint patterns - Drop the redundant stop_ibus_engine helper (engine covered via PID file) - Still remove ~/.local/share/vocalinux-ibus on uninstall (#559) - Align install.sh and correct the pkill tip in docs/UPDATE.md Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * fix(scripts): detect setuptools console-script Vocalinux processes Real installs run as `python …/bin/vocalinux`, so argv0 is python and the previous argv0-basename check missed the live app (instance.lock PID was skipped). Scan all cmdline args for …/bin/vocalinux or …/bin/vocalinux-gui instead, which still avoids matching a bare repo directory path. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
This commit is contained in:
co-authored by
Jatin K Malik
Cursor Agent
parent
44dac3b4ab
commit
40546e7f07
+8
-2
@@ -373,13 +373,19 @@ curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install
|
||||
### Old Version Still Running
|
||||
|
||||
```bash
|
||||
# Kill any running instances
|
||||
pkill -f vocalinux
|
||||
# Prefer stopping from the tray, or kill by the PID in the lock file:
|
||||
kill "$(tr -d '[:space:]' < "${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux/instance.lock")"
|
||||
|
||||
# If you used IBus injection, also stop the engine:
|
||||
kill "$(tr -d '[:space:]' < "${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux-ibus/engine.pid")"
|
||||
|
||||
# Start fresh
|
||||
vocalinux
|
||||
```
|
||||
|
||||
Avoid `pkill -f vocalinux` — it matches any process whose command line
|
||||
mentions the repo path (editors, shells, test runners) and can kill unrelated work.
|
||||
|
||||
### Missing Dependencies
|
||||
|
||||
If you see dependency errors:
|
||||
|
||||
+102
-22
@@ -44,27 +44,99 @@ print_kde_wayland_ibus_hint() {
|
||||
print_info "After changing it, restart Vocalinux or log out and back in."
|
||||
}
|
||||
|
||||
get_vocalinux_pids() {
|
||||
pgrep -f "vocalinux" 2>/dev/null | while read -r pid; do
|
||||
[ -z "$pid" ] && continue
|
||||
|
||||
if [ "$pid" = "$$" ] || [ "$pid" = "$PPID" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local stat
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null | awk '{print $1}')
|
||||
if [[ "$stat" == Z* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local cmd
|
||||
cmd=$(ps -o args= -p "$pid" 2>/dev/null)
|
||||
if [[ "$cmd" == *"install.sh"* ]] || [[ "$cmd" == *"uninstall.sh"* ]]; then
|
||||
continue
|
||||
fi
|
||||
# Read a numeric PID from a file (instance.lock / engine.pid). Empty if missing/invalid.
|
||||
read_pid_file() {
|
||||
local file="$1"
|
||||
local pid=""
|
||||
|
||||
[ -f "$file" ] || return 0
|
||||
pid=$(tr -d '[:space:]' < "$file" 2>/dev/null || true)
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]]; then
|
||||
echo "$pid"
|
||||
fi
|
||||
}
|
||||
|
||||
# True only for the Vocalinux app / IBus engine — not editors, shells, or tests
|
||||
# whose argv merely contains the string "vocalinux" (e.g. cwd under the repo).
|
||||
is_vocalinux_process() {
|
||||
local pid="$1"
|
||||
|
||||
if [ "$pid" = "$$" ] || [ "$pid" = "$PPID" ]; then
|
||||
return 1
|
||||
fi
|
||||
[ -r "/proc/$pid/cmdline" ] || return 1
|
||||
|
||||
local stat
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null | awk '{print $1}')
|
||||
if [[ "$stat" == Z* ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cmdline
|
||||
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)
|
||||
[ -n "$cmdline" ] || return 1
|
||||
|
||||
# Never target the installer/uninstaller themselves
|
||||
if [[ "$cmdline" == *"install.sh"* || "$cmdline" == *"uninstall.sh"* ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# python -m vocalinux.main [--flags]
|
||||
if [[ "$cmdline" == *"-m vocalinux.main"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# IBus engine: .../vocalinux/.../ibus_engine.py
|
||||
if [[ "$cmdline" == *"ibus_engine.py"* && "$cmdline" == *"vocalinux"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Console-script entrypoints. Setuptools scripts are executed as
|
||||
# `python …/bin/vocalinux`, so argv0 is python — scan every arg for the
|
||||
# script path (require …/bin/vocalinux to avoid matching a repo directory).
|
||||
local arg
|
||||
while IFS= read -r -d '' arg; do
|
||||
case "$arg" in
|
||||
vocalinux|vocalinux-gui|*/bin/vocalinux|*/bin/vocalinux-gui)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done < "/proc/$pid/cmdline"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Prefer the PIDs the app writes; fall back to narrow entrypoint patterns only.
|
||||
# Deliberately does NOT use `pgrep -f vocalinux` (matches editors/shells/cwd paths).
|
||||
get_vocalinux_pids() {
|
||||
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
local -A seen=()
|
||||
local pid candidate
|
||||
local -a candidates=()
|
||||
|
||||
for candidate in \
|
||||
"$(read_pid_file "$data_home/vocalinux/instance.lock")" \
|
||||
"$(read_pid_file "$data_home/vocalinux-ibus/engine.pid")"
|
||||
do
|
||||
[ -n "$candidate" ] && candidates+=("$candidate")
|
||||
done
|
||||
|
||||
# Fallback when PID files are missing/stale: exact process name or known argv.
|
||||
while read -r candidate; do
|
||||
[ -n "$candidate" ] && candidates+=("$candidate")
|
||||
done < <(
|
||||
pgrep -u "$(id -u)" -x vocalinux 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -x vocalinux-gui 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -f -- '-m vocalinux\.main' 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -f -- 'vocalinux/.*/ibus_engine\.py' 2>/dev/null || true
|
||||
)
|
||||
|
||||
for pid in "${candidates[@]}"; do
|
||||
[ -n "${seen[$pid]:-}" ] && continue
|
||||
seen[$pid]=1
|
||||
if is_vocalinux_process "$pid"; then
|
||||
echo "$pid"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
@@ -73,7 +145,13 @@ check_running_processes() {
|
||||
PIDS=$(get_vocalinux_pids || true)
|
||||
|
||||
if [ -n "$PIDS" ]; then
|
||||
print_warning "Found running Vocalinux process(es): $PIDS"
|
||||
print_warning "Found running Vocalinux process(es):"
|
||||
local pid
|
||||
for pid in $PIDS; do
|
||||
local cmd
|
||||
cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || echo "?")
|
||||
print_warning " PID $pid: $cmd"
|
||||
done
|
||||
echo ""
|
||||
|
||||
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
|
||||
@@ -89,7 +167,8 @@ check_running_processes() {
|
||||
fi
|
||||
|
||||
print_info "Stopping Vocalinux..."
|
||||
echo "$PIDS" | xargs -r kill -TERM 2>/dev/null || true
|
||||
# shellcheck disable=SC2086
|
||||
echo $PIDS | xargs -r kill -TERM 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
local REMAINING_PIDS
|
||||
@@ -97,7 +176,8 @@ check_running_processes() {
|
||||
|
||||
if [ -n "$REMAINING_PIDS" ]; then
|
||||
print_warning "Some processes still running, forcing termination..."
|
||||
echo "$REMAINING_PIDS" | xargs -r kill -KILL 2>/dev/null || true
|
||||
# shellcheck disable=SC2086
|
||||
echo $REMAINING_PIDS | xargs -r kill -KILL 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
|
||||
+112
-22
@@ -27,27 +27,99 @@ command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_vocalinux_pids() {
|
||||
pgrep -f "vocalinux" 2>/dev/null | while read -r pid; do
|
||||
[ -z "$pid" ] && continue
|
||||
|
||||
if [ "$pid" = "$$" ] || [ "$pid" = "$PPID" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local stat
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null | awk '{print $1}')
|
||||
if [[ "$stat" == Z* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local cmd
|
||||
cmd=$(ps -o args= -p "$pid" 2>/dev/null)
|
||||
if [[ "$cmd" == *"install.sh"* ]] || [[ "$cmd" == *"uninstall.sh"* ]]; then
|
||||
continue
|
||||
fi
|
||||
# Read a numeric PID from a file (instance.lock / engine.pid). Empty if missing/invalid.
|
||||
read_pid_file() {
|
||||
local file="$1"
|
||||
local pid=""
|
||||
|
||||
[ -f "$file" ] || return 0
|
||||
pid=$(tr -d '[:space:]' < "$file" 2>/dev/null || true)
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]]; then
|
||||
echo "$pid"
|
||||
fi
|
||||
}
|
||||
|
||||
# True only for the Vocalinux app / IBus engine — not editors, shells, or tests
|
||||
# whose argv merely contains the string "vocalinux" (e.g. cwd under the repo).
|
||||
is_vocalinux_process() {
|
||||
local pid="$1"
|
||||
|
||||
if [ "$pid" = "$$" ] || [ "$pid" = "$PPID" ]; then
|
||||
return 1
|
||||
fi
|
||||
[ -r "/proc/$pid/cmdline" ] || return 1
|
||||
|
||||
local stat
|
||||
stat=$(ps -o stat= -p "$pid" 2>/dev/null | awk '{print $1}')
|
||||
if [[ "$stat" == Z* ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cmdline
|
||||
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)
|
||||
[ -n "$cmdline" ] || return 1
|
||||
|
||||
# Never target the installer/uninstaller themselves
|
||||
if [[ "$cmdline" == *"install.sh"* || "$cmdline" == *"uninstall.sh"* ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# python -m vocalinux.main [--flags]
|
||||
if [[ "$cmdline" == *"-m vocalinux.main"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# IBus engine: .../vocalinux/.../ibus_engine.py
|
||||
if [[ "$cmdline" == *"ibus_engine.py"* && "$cmdline" == *"vocalinux"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Console-script entrypoints. Setuptools scripts are executed as
|
||||
# `python …/bin/vocalinux`, so argv0 is python — scan every arg for the
|
||||
# script path (require …/bin/vocalinux to avoid matching a repo directory).
|
||||
local arg
|
||||
while IFS= read -r -d '' arg; do
|
||||
case "$arg" in
|
||||
vocalinux|vocalinux-gui|*/bin/vocalinux|*/bin/vocalinux-gui)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done < "/proc/$pid/cmdline"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Prefer the PIDs the app writes; fall back to narrow entrypoint patterns only.
|
||||
# Deliberately does NOT use `pgrep -f vocalinux` (matches editors/shells/cwd paths).
|
||||
get_vocalinux_pids() {
|
||||
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
local -A seen=()
|
||||
local pid candidate
|
||||
local -a candidates=()
|
||||
|
||||
for candidate in \
|
||||
"$(read_pid_file "$data_home/vocalinux/instance.lock")" \
|
||||
"$(read_pid_file "$data_home/vocalinux-ibus/engine.pid")"
|
||||
do
|
||||
[ -n "$candidate" ] && candidates+=("$candidate")
|
||||
done
|
||||
|
||||
# Fallback when PID files are missing/stale: exact process name or known argv.
|
||||
while read -r candidate; do
|
||||
[ -n "$candidate" ] && candidates+=("$candidate")
|
||||
done < <(
|
||||
pgrep -u "$(id -u)" -x vocalinux 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -x vocalinux-gui 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -f -- '-m vocalinux\.main' 2>/dev/null || true
|
||||
pgrep -u "$(id -u)" -f -- 'vocalinux/.*/ibus_engine\.py' 2>/dev/null || true
|
||||
)
|
||||
|
||||
for pid in "${candidates[@]}"; do
|
||||
[ -n "${seen[$pid]:-}" ] && continue
|
||||
seen[$pid]=1
|
||||
if is_vocalinux_process "$pid"; then
|
||||
echo "$pid"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
@@ -81,7 +153,13 @@ kill_vocalinux_processes() {
|
||||
PIDS=$(get_vocalinux_pids || true)
|
||||
|
||||
if [ -n "$PIDS" ]; then
|
||||
print_warning "Found running Vocalinux process(es): $PIDS"
|
||||
print_warning "Found running Vocalinux process(es):"
|
||||
local pid
|
||||
for pid in $PIDS; do
|
||||
local cmd
|
||||
cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || echo "?")
|
||||
print_warning " PID $pid: $cmd"
|
||||
done
|
||||
|
||||
if [[ "$NON_INTERACTIVE" != "yes" ]]; then
|
||||
read -p "Kill running Vocalinux process(es)? (Y/n) " -n 1 -r
|
||||
@@ -93,7 +171,8 @@ kill_vocalinux_processes() {
|
||||
fi
|
||||
|
||||
print_info "Stopping Vocalinux..."
|
||||
echo "$PIDS" | xargs -r kill -TERM 2>/dev/null || true
|
||||
# shellcheck disable=SC2086
|
||||
echo $PIDS | xargs -r kill -TERM 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
local REMAINING_PIDS
|
||||
@@ -101,7 +180,8 @@ kill_vocalinux_processes() {
|
||||
|
||||
if [ -n "$REMAINING_PIDS" ]; then
|
||||
print_warning "Some processes still running, forcing termination..."
|
||||
echo "$REMAINING_PIDS" | xargs -r kill -KILL 2>/dev/null || true
|
||||
# shellcheck disable=SC2086
|
||||
echo $REMAINING_PIDS | xargs -r kill -KILL 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
@@ -173,6 +253,7 @@ done
|
||||
# Define XDG directories
|
||||
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/vocalinux"
|
||||
DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux"
|
||||
IBUS_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux-ibus"
|
||||
DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
||||
AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
|
||||
ICON_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps"
|
||||
@@ -307,6 +388,9 @@ remove_config_and_data() {
|
||||
else
|
||||
print_info "Keeping application data directory as requested: $DATA_DIR"
|
||||
fi
|
||||
|
||||
# IBus runtime data (socket, PID file, logs) is recreated on next launch.
|
||||
safe_remove "$IBUS_DATA_DIR" "IBus runtime data directory"
|
||||
}
|
||||
|
||||
# Function to clean up build artifacts
|
||||
@@ -405,6 +489,12 @@ verify_uninstallation() {
|
||||
print_warning "Application data directory still exists: $DATA_DIR"
|
||||
((ISSUES++))
|
||||
fi
|
||||
|
||||
# IBus runtime data should always be removed
|
||||
if [ -d "$IBUS_DATA_DIR" ]; then
|
||||
print_warning "IBus runtime data directory still exists: $IBUS_DATA_DIR"
|
||||
((ISSUES++))
|
||||
fi
|
||||
|
||||
# Return the number of issues found
|
||||
return $ISSUES
|
||||
|
||||
Reference in New Issue
Block a user