run: return after background startup

This commit is contained in:
2026-08-14 04:19:12 +09:00
parent 24e380651c
commit c2cb18cbdd
2 changed files with 28 additions and 303 deletions

View File

@@ -125,16 +125,18 @@ font-file arguments replace these defaults:
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
```
`run.sh` remains in the foreground and owns only the daemon and XIM process
that it starts. It waits for that daemon's IPC socket and IBus address file
before reporting success. With `DISPLAY` set it then starts XIM; without a
display it keeps the headless daemon in the foreground and does not start
XIM. A signal or XIM exit stops and reaps the launcher's daemon. On repeated
invocation, it stops a previous live `run.sh` only after confirming that the
old launcher directly owns the daemon serving the same IPC and IBus
endpoints and that the daemon carries that launcher's PID and process-start
identity. A manually started or orphaned daemon is left untouched and
reported as a visible startup failure.
`run.sh` uses `pkill` to stop this user's existing `strans` and `strans-xim`
processes, starts fresh ones in the background, and returns. Run the same
command again to restart them. Without `DISPLAY`, it starts only the daemon.
An X startup hook can invoke it directly before starting the desktop session:
```sh
/path/to/strans/run.sh
exec /bin/sh /etc/xdg/xfce4/xinitrc "$@"
```
The launcher targets Linux and requires `pkill` from procps-ng.
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
`XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user

309
run.sh
View File

@@ -1,8 +1,6 @@
#!/bin/sh
cd "$(dirname "$0")" || exit 1
root=$(pwd -P) || exit 1
uid=$(id -u) || exit 1
if test "$#" -eq 0; then
set --
@@ -12,9 +10,7 @@ if test "$#" -eq 0; then
/usr/share/fonts/TTF/Jigmo2.ttf \
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
do
if test -f "$font"; then
set -- "$@" "$font"
fi
test -f "$font" && set -- "$@" "$font"
done
if test "$#" -eq 0; then
echo "run.sh: no default font found; pass a font file" >&2
@@ -30,297 +26,24 @@ if test -n "${DISPLAY-}" && ! test -x xim/strans-xim; then
echo "run.sh: xim/strans-xim is not executable; run make first" >&2
exit 1
fi
case ${XDG_RUNTIME_DIR-} in
/) ipc=/strans.sock ;;
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
*) ipc=/tmp/strans.$(id -u) ;;
esac
if test -n "${XDG_CONFIG_HOME-}"; then
busdir=$XDG_CONFIG_HOME/ibus/bus
elif test "${HOME+x}" = x; then
busdir=$HOME/.config/ibus/bus
else
echo "run.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
if ! command -v pkill >/dev/null 2>&1; then
echo "run.sh: pkill is required" >&2
exit 1
fi
strans_pid=
xim_pid=
old_daemon_pid=
old_daemon_start=
old_launcher_pid=
old_launcher_start=
uid=$(id -u) || exit 1
pkill -TERM -u "$uid" -x strans-xim 2>/dev/null || :
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
sleep 0.1
pkill -KILL -u "$uid" -x strans-xim 2>/dev/null || :
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
cleanup()
{
status=$?
trap - 0 1 2 15
if test -n "$xim_pid"; then
kill "$xim_pid" 2>/dev/null || :
wait "$xim_pid" 2>/dev/null || :
xim_pid=
fi
if test -n "$strans_pid"; then
kill "$strans_pid" 2>/dev/null || :
wait "$strans_pid" 2>/dev/null || :
strans_pid=
fi
exit "$status"
}
ibusready()
{
for address in "$busdir"/*; do
test -f "$address" || continue
foundpid=
foundaddress=
while IFS= read -r line; do
case $line in
IBUS_DAEMON_PID="$strans_pid") foundpid=1 ;;
IBUS_ADDRESS=unix:abstract=strans-"$strans_pid",*) foundaddress=1 ;;
esac
done <"$address"
if test -n "$foundpid" && test -n "$foundaddress"; then
return 0
fi
done
return 1
}
procuid()
{
{
while IFS=' ' read -r key real effective saved filesystem rest; do
case $key in
Uid:)
test "$real" = "$effective" &&
test "$real" = "$saved" &&
test "$real" = "$filesystem" || return 1
printf '%s\n' "$real"
return 0
;;
esac
done <"/proc/$1/status"
} 2>/dev/null
return 1
}
procparent()
{
{
while IFS=' ' read -r key value rest; do
case $key in
PPid:) printf '%s\n' "$value"; return 0 ;;
esac
done <"/proc/$1/status"
} 2>/dev/null
return 1
}
procstart()
{
proc_stat=
{ IFS= read -r proc_stat <"/proc/$1/stat"; } 2>/dev/null || return 1
proc_stat=${proc_stat##*) }
set -- $proc_stat
test "$#" -ge 20 || return 1
test "$1" != Z || return 1
printf '%s\n' "${20}"
}
sameproc()
{
test -n "$2" || return 1
proc_now=$(procstart "$1") || return 1
test "$proc_now" = "$2"
}
listeninginode()
{
listener_inode=
listener_count=0
{
while IFS=' ' read -r num ref proto flags type state inode path; do
if test "$flags" = 00010000 && test "$type" = 0001 &&
test "$state" = 01 && test "$path" = "$1"; then
listener_inode=$inode
listener_count=$((listener_count + 1))
fi
done </proc/net/unix
} 2>/dev/null
test "$listener_count" -eq 1 || return 1
printf '%s\n' "$listener_inode"
}
socketowned()
{
socket_inode=$(listeninginode "$2") || return 1
for socket_fd in "/proc/$1/fd"/*; do
test "$(readlink "$socket_fd" 2>/dev/null)" = \
"socket:[$socket_inode]" && return 0
done
return 1
}
validdaemon()
{
test "$(procuid "$1")" = "$uid" || return 1
test "$(readlink "/proc/$1/cwd" 2>/dev/null)" = "$root" || return 1
daemon_exe=$(readlink "/proc/$1/exe" 2>/dev/null) || return 1
case $daemon_exe in
"$root/strans"|"$root/strans (deleted)") ;;
*) return 1 ;;
esac
test -S "$ipc" || return 1
socketowned "$1" "$ipc" || return 1
socketowned "$1" "@strans-$1"
}
finddaemon()
{
old_daemon_pid=
old_daemon_start=
for address in "$busdir"/*; do
test -f "$address" || continue
address_pid=
address_ok=
while IFS= read -r line; do
case $line in
IBUS_DAEMON_PID=*) address_pid=${line#IBUS_DAEMON_PID=} ;;
esac
done <"$address"
case $address_pid in
''|*[!0-9]*) continue ;;
esac
while IFS= read -r line; do
case $line in
IBUS_ADDRESS=unix:abstract=strans-"$address_pid",*)
address_ok=1 ;;
esac
done <"$address"
test -n "$address_ok" || continue
validdaemon "$address_pid" || continue
address_start=$(procstart "$address_pid") || continue
validdaemon "$address_pid" || continue
old_daemon_pid=$address_pid
old_daemon_start=$address_start
return 0
done
return 1
}
findlauncher()
{
launcher_env=$({ tr '\000' '\n' <"/proc/$old_daemon_pid/environ"; } \
2>/dev/null) || return 1
old_launcher_pid=$(printf '%s\n' "$launcher_env" |
sed -n 's/^STRANS_RUN_OWNER_PID=//p')
old_launcher_start=$(printf '%s\n' "$launcher_env" |
sed -n 's/^STRANS_RUN_OWNER_START=//p')
case $old_launcher_pid in
''|*[!0-9]*|0|1|"$$") return 1 ;;
esac
case $old_launcher_start in
''|*[!0-9]*) return 1 ;;
esac
test "$(procuid "$old_launcher_pid")" = "$uid" || return 1
test "$(readlink "/proc/$old_launcher_pid/cwd" 2>/dev/null)" = "$root" ||
return 1
sameproc "$old_launcher_pid" "$old_launcher_start" || return 1
sameproc "$old_daemon_pid" "$old_daemon_start" || return 1
test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1
return 0
}
stoplauncher()
{
sameproc "$old_daemon_pid" "$old_daemon_start" || return 1
test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1
sameproc "$old_launcher_pid" "$old_launcher_start" || return 1
kill "$old_launcher_pid" 2>/dev/null || return 1
echo "run.sh: stopping previous launcher $old_launcher_pid (daemon $old_daemon_pid)"
stop_attempt=0
while test "$stop_attempt" -lt 10; do
if ! sameproc "$old_launcher_pid" "$old_launcher_start" &&
! sameproc "$old_daemon_pid" "$old_daemon_start"; then
return 0
fi
stop_attempt=$((stop_attempt + 1))
if test "$stop_attempt" -lt 10; then
sleep 1
fi
done
echo "run.sh: previous launcher did not stop within 10 seconds" >&2
return 1
}
trap cleanup 0
trap 'exit 129' 1
trap 'exit 130' 2
trap 'exit 143' 15
launcher_start=$(procstart "$$") || {
echo "run.sh: cannot read launcher process identity from /proc" >&2
exit 1
}
if finddaemon; then
if ! findlauncher; then
echo "run.sh: daemon $old_daemon_pid owns the current endpoints but has no" >&2
echo "run.sh: live run.sh owner; leaving it untouched (stop it once, then retry)" >&2
exit 1
fi
if ! stoplauncher; then
echo "run.sh: previous launcher did not stop cleanly; refusing to start another daemon" >&2
exit 1
fi
fi
STRANS_RUN_OWNER_PID=$$ STRANS_RUN_OWNER_START=$launcher_start \
./strans map "$@" &
./strans map "$@" </dev/null &
strans_pid=$!
attempt=0
while test "$attempt" -lt 10; do
if test -S "$ipc" && ibusready; then
break
fi
if ! kill -0 "$strans_pid" 2>/dev/null; then
wait "$strans_pid"
status=$?
strans_pid=
if test "$status" -eq 0; then
status=1
fi
echo "run.sh: daemon exited before publishing IPC and IBus endpoints" >&2
exit "$status"
fi
attempt=$((attempt + 1))
if test "$attempt" -lt 10; then
sleep 1
fi
done
if test "$attempt" -eq 10; then
echo "run.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
exit 1
fi
echo "run.sh: daemon $strans_pid is ready"
if test -z "${DISPLAY-}"; then
echo "run.sh: DISPLAY is unset; XIM will not be started"
wait "$strans_pid"
status=$?
strans_pid=
exit "$status"
fi
xim/strans-xim &
if test -n "${DISPLAY-}"; then
xim/strans-xim </dev/null &
xim_pid=$!
echo "run.sh: started XIM $xim_pid; press Ctrl-C to stop both processes"
wait "$xim_pid"
status=$?
xim_pid=
exit "$status"
echo "run.sh: started daemon $strans_pid and XIM $xim_pid"
else
echo "run.sh: started daemon $strans_pid; DISPLAY is unset"
fi