Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README-JA.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ curl -fsSL https://raw.githubusercontent.com/cotore-game/mcserver-compose-kit/ma

```bash
curl -fsSL https://raw.githubusercontent.com/cotore-game/mcserver-compose-kit/main/install.sh | \
bash -s -- --version v1.1.3
bash -s -- --version v1.1.4
```

インストーラーはリリースの圧縮ファイルを取得し、SHA-256を検証して`~/.local/share/mcserver-compose-kit`へ配置します。また、`~/.local/bin`用のPATH設定を管理ブロックとして`~/.bashrc`へ追加します。
Expand Down Expand Up @@ -93,6 +93,8 @@ mcserver-kit

ホーム画面から、サーバー作成・管理、全体設定、MCIDテンプレート、言語、動作環境診断、ヘルプへ進めます。矢印キーで選択し、Enterで決定します。

サーバー操作や更新確認の実行中は、ひとつの待機画面を開いたまま点字スピナーを表示します。コマンドの出力は一時ファイルに蓄積し、終了後にエラーも含めて表示します。アニメーションのたびに画面を開閉する方式は廃止しました。メニューやボタンの配置・操作は従来どおりで、メニュー間の画面遷移自体を刷新する修正ではありません。

インストールされているツールのバージョンは次で確認できます。

```bash
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Install a specific release:

```bash
curl -fsSL https://raw.githubusercontent.com/cotore-game/mcserver-compose-kit/main/install.sh | \
bash -s -- --version v1.1.3
bash -s -- --version v1.1.4
```

The installer downloads the release archive, verifies its SHA-256 checksum, and installs the program under `~/.local/share/mcserver-compose-kit`. It also adds a managed PATH block for `~/.local/bin` to `~/.bashrc`.
Expand Down Expand Up @@ -92,6 +92,8 @@ mcserver-kit

The dashboard includes server creation and management, global settings, MCID templates, language selection, diagnostics, and help. Use the arrow keys to select an item and Enter to open it.

While server actions or update checks run, a single waiting dialog shows a braille spinner. Command output is collected in a temporary file and displayed afterward, including errors. The waiting dialog no longer opens and closes for each animation frame. Menus, buttons, and navigation retain the existing layout; this fix does not redesign transitions between menus.

Check the installed toolkit version with:

```bash
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.3
1.1.4
13 changes: 3 additions & 10 deletions libexec/mcserver-kit/home-tui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,10 @@ pause_for_enter() {
run_and_show() {
local title="$1"
shift
local output pid result=0 frame_index=0
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local output result=0
new_temp_file output
"$@" >"$output" 2>&1 &
pid=$!
while kill -0 "$pid" 2>/dev/null; do
whiptail --title "$title" --infobox "$(tr home.processing "${frames[frame_index]}" "$title")" 8 72
frame_index=$(((frame_index + 1) % ${#frames[@]}))
sleep 0.15
done
wait "$pid" || result=$?
python3 "${SCRIPT_DIR}/tui-progress.py" --output "$output" --title "$title" \
--message "$(tr home.processing '{spinner}' "$title")" -- "$@" || result=$?
if [[ ! -s "$output" ]]; then
if ((result == 0)); then
tr home.completed >"$output"
Expand Down
138 changes: 138 additions & 0 deletions libexec/mcserver-kit/tui-progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Keep a single progress dialog open while a non-interactive command runs.

Only the waiting screen uses curses. Menus and result dialogs remain whiptail.
"""
from __future__ import annotations

import argparse
import curses
import os
from pathlib import Path
import signal
import subprocess
import sys
import unicodedata

FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"


def clipped(text: str, width: int) -> str:
result, used = [], 0
for char in text:
if unicodedata.category(char).startswith("C"):
continue
size = 0 if unicodedata.combining(char) else (2 if unicodedata.east_asian_width(char) in "WF" else 1)
if used + size > width:
break
result.append(char)
used += size
return "".join(result)


def stop_command(process):
# Include grandchildren, not just the shell launching Docker or curl.
if process.poll() is None:
try:
os.killpg(process.pid, signal.SIGTERM)
except ProcessLookupError:
process.wait()
return
try:
process.wait(timeout=3)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()


def execute(screen, command, output, title, message):
window = None
size = None
if screen is not None:
curses.curs_set(0)
screen.timeout(100)

def draw(frame):
nonlocal window, size
if screen is None:
return
current = screen.getmaxyx()
if current != size:
size = current
rows, columns = size
screen.erase()
screen.noutrefresh()
if rows < 4 or columns < 12:
window = None
else:
height, width = min(8, rows), min(72, columns)
window = curses.newwin(height, width, (rows - height) // 2, (columns - width) // 2)
window.box()
window.addstr(0, 2, clipped(" " + title + " ", width - 4))
if window is not None:
height, width = window.getmaxyx()
row = min(3, height - 2)
window.move(row, 2)
window.addstr(clipped(message.replace("{spinner}", frame), width - 4))
window.noutrefresh()
curses.doupdate()

draw(FRAMES[0]) # Show the dialog before launching a potentially slow command.
with subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=output,
stderr=subprocess.STDOUT, start_new_session=True) as process:
try:
frame = 0
while process.poll() is None:
if screen is None:
try:
process.wait(timeout=0.1)
except subprocess.TimeoutExpired:
pass
else:
draw(FRAMES[frame % len(FRAMES)])
screen.getch() # Consume keys; they must not dismiss the next result.
frame += 1
if screen is not None:
curses.flushinp()
return process.returncode if process.returncode >= 0 else 128 - process.returncode
finally:
stop_command(process)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True)
parser.add_argument("--title", required=True)
parser.add_argument("--message", required=True)
parser.add_argument("command", nargs=argparse.REMAINDER)
args = parser.parse_args()
command = args.command[1:] if args.command[:1] == ["--"] else args.command
if not command:
parser.error("a command is required after --")

def interrupted(signum, _frame):
raise InterruptedError(signum)

previous = signal.signal(signal.SIGTERM, interrupted)
try:
with Path(args.output).open("w", encoding="utf-8") as output:
try:
if sys.stdin.isatty() and sys.stdout.isatty():
return curses.wrapper(execute, command, output, args.title, args.message)
# CI and redirected invocations have no interactive display.
return execute(None, command, output, args.title, args.message)
except KeyboardInterrupt:
print("Command interrupted (SIGINT).", file=output)
return 130
except InterruptedError:
print("Command interrupted (SIGTERM).", file=output)
return 143
except (OSError, curses.error) as error:
print(f"Could not complete command: {error}", file=output)
return 1
finally:
signal.signal(signal.SIGTERM, previous)


if __name__ == "__main__":
raise SystemExit(main())
3 changes: 2 additions & 1 deletion tests/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ CURL
MCSERVER_KIT_UPDATE_CACHE_DIR="${temp_dir}/home-transition/cache" \
MCSERVER_KIT_TEST_EVENTS="$event_log" MCSERVER_KIT_TEST_MENU_COUNT="$menu_count" \
bash "${REPO_ROOT}/mcserver-kit" home)"
assert_equal 'present' "$(grep -Fq 'loading' "$event_log" && printf present)" 'update checks retain a progress dialog'
assert_equal 'absent' "$(grep -Fq 'loading' "$event_log" && printf present || printf absent)" 'progress no longer repeatedly opens whiptail infobox'
assert_equal 'present' "$(grep -Fq 'result' "$event_log" && printf present)" 'update result is shown in a persistent dialog'
assert_equal 'menu' "$(tail -n 1 "$event_log")" 'the home menu returns after viewing the update result'
}
Expand Down Expand Up @@ -950,6 +950,7 @@ WHIPTAIL
}

main() {
PYTHONDONTWRITEBYTECODE=1 python3 "${REPO_ROOT}/tests/test-tui-progress.py"
TEST_TEMP_DIR="$(mktemp -d)"
trap cleanup EXIT

Expand Down
Loading
Loading