update at 2026-03-16 09:00:35

This commit is contained in:
douboer@gmail.com
2026-03-16 09:00:35 +08:00
parent 4b280073d4
commit 3d8dba12aa
24 changed files with 974 additions and 233 deletions

View File

@@ -16,12 +16,21 @@ SLEEP_SCREEN_INTERVAL=${SLEEP_SCREEN_INTERVAL:-3600}
DISABLE_SYSTEM_SUSPEND=${DISABLE_SYSTEM_SUSPEND:-false}
BACKGROUND_REFRESH_INTERVAL_MINUTES=${BACKGROUND_REFRESH_INTERVAL_MINUTES:-120}
CLOCK_FULL_REFRESH_INTERVAL_MINUTES=${CLOCK_FULL_REFRESH_INTERVAL_MINUTES:-15}
PRE_SLEEP_GRACE_SECONDS=${PRE_SLEEP_GRACE_SECONDS:-10}
STATUS_MASK_ENABLED=${STATUS_MASK_ENABLED:-true}
STATUS_MASK_LEFT=${STATUS_MASK_LEFT:-700}
STATUS_MASK_TOP=${STATUS_MASK_TOP:-0}
STATUS_MASK_WIDTH=${STATUS_MASK_WIDTH:-372}
STATUS_MASK_HEIGHT=${STATUS_MASK_HEIGHT:-24}
STATUS_MASK_PASSES=${STATUS_MASK_PASSES:-3}
STATUS_MASK_DELAY_SECONDS=${STATUS_MASK_DELAY_SECONDS:-1}
RTC=/sys/devices/platform/mxc_rtc.0/wakeup_enable
LOW_BATTERY_REPORTING=${LOW_BATTERY_REPORTING:-false}
LOW_BATTERY_THRESHOLD_PERCENT=${LOW_BATTERY_THRESHOLD_PERCENT:-10}
num_refresh=0
background_needs_redraw=true
init() {
if [ -z "$TIMEZONE" ] || [ -z "$REFRESH_SCHEDULE" ]; then
@@ -38,16 +47,28 @@ init() {
echo "System suspend disabled, using normal sleep between refreshes."
fi
/etc/init.d/framework stop
stop_framework
initctl stop webreader >/dev/null 2>&1
echo powersave >/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
lipc-set-prop com.lab126.powerd preventScreenSaver 1
}
stop_framework() {
# 不同 Kindle 固件停止 framework 的入口不完全一致。
# Voyage 5.13.6 上没有 /etc/init.d/framework需要走 upstart 入口。
if [ -x /etc/init.d/framework ]; then
/etc/init.d/framework stop || true
return
fi
stop framework >/dev/null 2>&1 || initctl stop framework >/dev/null 2>&1 || true
}
prepare_sleep() {
echo "Preparing sleep"
/usr/sbin/eips -f -g "$DIR/sleeping.png"
background_needs_redraw=true
# Give screen time to refresh
sleep 2
@@ -97,6 +118,27 @@ clock_force_full_refresh() {
[ $((minute % CLOCK_FULL_REFRESH_INTERVAL_MINUTES)) -eq 0 ]
}
mask_system_status_overlay() {
if [ "$STATUS_MASK_ENABLED" != true ]; then
return
fi
# Voyage 上 framework/appmgrd 偶尔会把右上角时间与状态图标重新画回屏幕。
# 这里在每次 dashboard 刷新后,用顶部空白带的白底把它盖掉。
# 实测需要延迟后再补盖一次,否则系统可能会在我们第一次覆盖后再重画一遍。
pass=1
while [ "$pass" -le "$STATUS_MASK_PASSES" ]; do
fbink -q -V -B WHITE -k \
"top=$STATUS_MASK_TOP,left=$STATUS_MASK_LEFT,width=$STATUS_MASK_WIDTH,height=$STATUS_MASK_HEIGHT"
if [ "$pass" -lt "$STATUS_MASK_PASSES" ] && [ "$STATUS_MASK_DELAY_SECONDS" -gt 0 ]; then
sleep "$STATUS_MASK_DELAY_SECONDS"
fi
pass=$((pass + 1))
done
}
refresh_dashboard() {
background_refreshed=false
@@ -116,6 +158,16 @@ refresh_dashboard() {
return 1
fi
if [ "$background_refreshed" = false ] && [ "$background_needs_redraw" = true ]; then
echo "Restoring cached background"
/usr/sbin/eips -f -g "$BACKGROUND_PNG"
background_needs_redraw=false
fi
if [ "$background_refreshed" = true ]; then
background_needs_redraw=false
fi
if [ "$background_refreshed" = true ] || clock_force_full_refresh; then
echo "Clock patch full refresh"
"$CLOCK_RENDER_CMD" true
@@ -124,18 +176,40 @@ refresh_dashboard() {
"$CLOCK_RENDER_CMD" false
fi
mask_system_status_overlay
num_refresh=$((num_refresh + 1))
}
powerd_get_prop() {
prop_name=$1
if ! command -v lipc-get-prop >/dev/null 2>&1; then
echo "unavailable"
return 0
fi
lipc-get-prop com.lab126.powerd "$prop_name" 2>/dev/null || echo "unavailable"
}
log_battery_stats() {
battery_level=$(gasgauge-info -c)
echo "$(date) Battery level: $battery_level."
battery_level=$(gasgauge-info -c 2>/dev/null || echo "unknown")
charging_state=$(powerd_get_prop isCharging)
battery_state_info=$(powerd_get_prop battStateInfo)
# 同时记录 powerd 的充电标志与原始电池状态,便于直接从 dash.log
# 判断 Kindle 是否识别到外部供电,以及是否真的在充电。
echo "$(date) Battery level: $battery_level. isCharging: $charging_state. battStateInfo: $battery_state_info."
if [ "$LOW_BATTERY_REPORTING" = true ]; then
battery_level_numeric=${battery_level%?}
if [ "$battery_level_numeric" -le "$LOW_BATTERY_THRESHOLD_PERCENT" ]; then
"$LOW_BATTERY_CMD" "$battery_level_numeric"
fi
case "$battery_level" in
*%)
battery_level_numeric=${battery_level%?}
if [ "$battery_level_numeric" -le "$LOW_BATTERY_THRESHOLD_PERCENT" ]; then
"$LOW_BATTERY_CMD" "$battery_level_numeric"
fi
;;
esac
fi
}
@@ -160,20 +234,31 @@ main_loop() {
next_wakeup_secs=$("$DIR/next-wakeup" --schedule="$REFRESH_SCHEDULE" --timezone="$TIMEZONE")
if [ "$next_wakeup_secs" -gt "$SLEEP_SCREEN_INTERVAL" ]; then
if [ "$next_wakeup_secs" -gt "$SLEEP_SCREEN_INTERVAL" ] && [ "$DISABLE_SYSTEM_SUSPEND" != true ]; then
action="sleep"
prepare_sleep
else
if [ "$next_wakeup_secs" -gt "$SLEEP_SCREEN_INTERVAL" ] && [ "$DISABLE_SYSTEM_SUSPEND" = true ]; then
echo "Debug mode active, skipping sleeping screen."
fi
action="suspend"
refresh_dashboard
fi
# take a bit of time before going to sleep, so this process can be aborted
sleep 10
actual_sleep_secs=$next_wakeup_secs
if [ "$actual_sleep_secs" -gt "$PRE_SLEEP_GRACE_SECONDS" ]; then
actual_sleep_secs=$((actual_sleep_secs - PRE_SLEEP_GRACE_SECONDS))
else
actual_sleep_secs=0
fi
# 预留一小段可中断窗口,便于在 Kindle 本机或 SSH 下手动终止进程。
# 这段时间必须从 rtc_sleep 中扣掉,否则每分钟刷新会长期晚于计划时间。
sleep "$PRE_SLEEP_GRACE_SECONDS"
echo "Going to $action, next wakeup in ${next_wakeup_secs}s"
rtc_sleep "$next_wakeup_secs"
rtc_sleep "$actual_sleep_secs"
done
}

View File

@@ -35,7 +35,9 @@ END {
mv "$TMP_FILE" "$ENV_FILE"
# 已运行的 dashboard 进程不会重新读取 env.sh切换后先停掉它
# 避免旧进程继续按旧配置运行
# 然后立刻拉起新的 dashboard避免用户还要再次手动启动
pkill -f "$DIR/dash.sh" 2>/dev/null || true
sleep 1
"$DIR/start.sh"
echo "已关闭 Dashboard 调试模式。当前 Dashboard 已停止,请重新启动 Kindle Dashboard。"
echo "已关闭 Dashboard 调试模式,并自动重启 Kindle Dashboard。"

View File

@@ -35,7 +35,9 @@ END {
mv "$TMP_FILE" "$ENV_FILE"
# 已运行的 dashboard 进程不会重新读取 env.sh切换后先停掉它
# 避免旧进程继续按旧配置进入系统挂起
# 然后立刻拉起新的 dashboard避免用户还要在短时间内再点一次菜单
pkill -f "$DIR/dash.sh" 2>/dev/null || true
sleep 1
"$DIR/start.sh"
echo "已开启 Dashboard 调试模式。当前 Dashboard 已停止,请重新启动 Kindle Dashboard。"
echo "已开启 Dashboard 调试模式,并自动重启 Kindle Dashboard。"

View File

@@ -11,8 +11,24 @@ to_decimal() {
}'
}
hour_value=${1:-$(date '+%H')}
minute_value=${2:-$(date '+%M')}
current_clock_values() {
# 时钟渲染要和 dashboard 配置的时区保持一致。
# 否则即使 next-wakeup 按 TIMEZONE 唤醒,指针仍会按系统默认时区取值。
if [ -n "${TIMEZONE:-}" ]; then
TZ="$TIMEZONE" date '+%H %M'
else
date '+%H %M'
fi
}
if [ "$#" -ge 2 ]; then
hour_value=$1
minute_value=$2
else
set -- $(current_clock_values)
hour_value=$1
minute_value=$2
fi
hour_decimal=$(to_decimal "$hour_value")
minute_decimal=$(to_decimal "$minute_value")

View File

@@ -13,6 +13,37 @@ export CLOCK_REGION_WIDTH=${CLOCK_REGION_WIDTH:-220}
export CLOCK_REGION_HEIGHT=${CLOCK_REGION_HEIGHT:-220}
export CLOCK_FULL_REFRESH_INTERVAL_MINUTES=${CLOCK_FULL_REFRESH_INTERVAL_MINUTES:-15}
# 本机时钟外观参数:
# 页面改版导致时钟区域尺寸变化时,通常只需要改 CLOCK_REGION_*
# 这组比例参数会随新的宽高自动缩放。
# 如果只是想微调指针长短、粗细或刻度长度,再改下面这些值即可,不用改 Lua 代码。
export CLOCK_FACE_RADIUS_RATIO=${CLOCK_FACE_RADIUS_RATIO:-0.47}
export CLOCK_FACE_STROKE=${CLOCK_FACE_STROKE:-3}
export CLOCK_TICK_OUTER_INSET=${CLOCK_TICK_OUTER_INSET:-6}
export CLOCK_MAJOR_TICK_LENGTH=${CLOCK_MAJOR_TICK_LENGTH:-14}
export CLOCK_MINOR_TICK_LENGTH=${CLOCK_MINOR_TICK_LENGTH:-7}
export CLOCK_MAJOR_TICK_THICKNESS=${CLOCK_MAJOR_TICK_THICKNESS:-4}
export CLOCK_MINOR_TICK_THICKNESS=${CLOCK_MINOR_TICK_THICKNESS:-2}
export CLOCK_HOUR_LENGTH_RATIO=${CLOCK_HOUR_LENGTH_RATIO:-0.48}
export CLOCK_MINUTE_LENGTH_RATIO=${CLOCK_MINUTE_LENGTH_RATIO:-0.72}
export CLOCK_HOUR_THICKNESS=${CLOCK_HOUR_THICKNESS:-9}
export CLOCK_MINUTE_THICKNESS=${CLOCK_MINUTE_THICKNESS:-5}
export CLOCK_CENTER_RADIUS=${CLOCK_CENTER_RADIUS:-7}
# 进入 rtc suspend 前预留的可中断窗口,方便在调试时及时停止进程。
# 这段时间会从真正的休眠时长里扣掉,避免分钟刷新慢一拍。
export PRE_SLEEP_GRACE_SECONDS=${PRE_SLEEP_GRACE_SECONDS:-10}
# Voyage 顶部状态栏遮罩用于压住系统偶尔重画出来的时间、Wi-Fi、电池图标。
# 当前坐标只覆盖页面顶部空白带,不会擦到天气卡上边框。
export STATUS_MASK_ENABLED=${STATUS_MASK_ENABLED:-true}
export STATUS_MASK_LEFT=${STATUS_MASK_LEFT:-700}
export STATUS_MASK_TOP=${STATUS_MASK_TOP:-0}
export STATUS_MASK_WIDTH=${STATUS_MASK_WIDTH:-372}
export STATUS_MASK_HEIGHT=${STATUS_MASK_HEIGHT:-24}
export STATUS_MASK_PASSES=${STATUS_MASK_PASSES:-3}
export STATUS_MASK_DELAY_SECONDS=${STATUS_MASK_DELAY_SECONDS:-1}
# By default, partial screen updates are used to update the screen,
# to prevent the screen from flashing. After a few partial updates,
# the screen will start to look a bit distorted (due to e-ink ghosting).
@@ -21,7 +52,7 @@ export CLOCK_FULL_REFRESH_INTERVAL_MINUTES=${CLOCK_FULL_REFRESH_INTERVAL_MINUTES
export FULL_DISPLAY_REFRESH_RATE=${FULL_DISPLAY_REFRESH_RATE:-0}
# 调试开关:设为 true 后,主循环仍会按计划拉图和刷新屏幕,
# 不会把 Kindle 写入 /sys/power/state 进入系统挂起。
# 不会进入 sleeping.png 分支,也不会把 Kindle 写入 /sys/power/state 进入系统挂起。
# 适合通过 KUAL 或普通 start.sh 连续观察效果,调试结束后再改回 false。
export DISABLE_SYSTEM_SUSPEND=${DISABLE_SYSTEM_SUSPEND:-false}

View File

@@ -0,0 +1,157 @@
-- 在 Kindle 本机生成一张时钟区域位图,然后交给 fbink 刷到屏幕。
-- 这里不再依赖透明 PNG 叠图,避免 eips 处理 alpha 时出现整块发黑的问题。
local output_path = assert(arg[1], "missing output path")
local width = tonumber((assert(arg[2], "missing width")))
local height = tonumber((assert(arg[3], "missing height")))
local hour_value = tonumber((assert(arg[4], "missing hour")))
local minute_value = tonumber((assert(arg[5], "missing minute")))
local function number_arg(index, fallback)
local value = arg[index]
if value == nil or value == "" then
return fallback
end
local numeric = tonumber(value)
if numeric == nil then
return fallback
end
return numeric
end
local WHITE = 255
local BLACK = 0
local cx = (width - 1) / 2
local cy = (height - 1) / 2
local face_radius = math.floor(math.min(width, height) * number_arg(6, 0.47))
local face_stroke = number_arg(7, 3)
local tick_outer_inset = number_arg(8, 6)
local major_tick_length = number_arg(9, 14)
local minor_tick_length = number_arg(10, 7)
local major_tick_thickness = number_arg(11, 4)
local minor_tick_thickness = number_arg(12, 2)
local hour_length_ratio = number_arg(13, 0.48)
local minute_length_ratio = number_arg(14, 0.72)
local hour_thickness = number_arg(15, 9)
local minute_thickness = number_arg(16, 5)
local center_radius = number_arg(17, 7)
local pixels = {}
for index = 1, width * height do
pixels[index] = WHITE
end
local function pixel_index(x, y)
return y * width + x + 1
end
local function set_pixel(x, y, value)
if x < 0 or y < 0 or x >= width or y >= height then
return
end
pixels[pixel_index(x, y)] = value
end
local function fill_disk(x, y, radius, value)
local r2 = radius * radius
local min_x = math.floor(x - radius)
local max_x = math.ceil(x + radius)
local min_y = math.floor(y - radius)
local max_y = math.ceil(y + radius)
for py = min_y, max_y do
for px = min_x, max_x do
local dx = px - x
local dy = py - y
if dx * dx + dy * dy <= r2 then
set_pixel(px, py, value)
end
end
end
end
local function draw_segment(x1, y1, x2, y2, thickness, value)
local dx = x2 - x1
local dy = y2 - y1
local steps = math.max(math.abs(dx), math.abs(dy))
if steps == 0 then
fill_disk(x1, y1, math.max(1, thickness / 2), value)
return
end
local radius = math.max(1, thickness / 2)
for step = 0, steps do
local t = step / steps
local x = x1 + dx * t
local y = y1 + dy * t
fill_disk(x, y, radius, value)
end
end
local function draw_circle(radius, thickness, value)
local samples = 720
for step = 0, samples - 1 do
local angle = (step / samples) * math.pi * 2
local x = cx + math.cos(angle) * radius
local y = cy + math.sin(angle) * radius
fill_disk(x, y, thickness / 2, value)
end
end
local function draw_ticks()
for tick = 0, 59 do
local is_major = tick % 5 == 0
local angle = (tick / 60) * math.pi * 2 - math.pi / 2
local outer = face_radius - tick_outer_inset
local inner = outer - (is_major and major_tick_length or minor_tick_length)
local x1 = cx + math.cos(angle) * inner
local y1 = cy + math.sin(angle) * inner
local x2 = cx + math.cos(angle) * outer
local y2 = cy + math.sin(angle) * outer
draw_segment(x1, y1, x2, y2, is_major and major_tick_thickness or minor_tick_thickness, BLACK)
end
end
local function hand_endpoint(angle_deg, length)
local angle = math.rad(angle_deg - 90)
return cx + math.cos(angle) * length, cy + math.sin(angle) * length
end
local function draw_hands()
local hour_angle = (hour_value % 12) * 30 + minute_value * 0.5
local minute_angle = minute_value * 6
local hour_x, hour_y = hand_endpoint(hour_angle, face_radius * hour_length_ratio)
local minute_x, minute_y = hand_endpoint(minute_angle, face_radius * minute_length_ratio)
draw_segment(cx, cy, hour_x, hour_y, hour_thickness, BLACK)
draw_segment(cx, cy, minute_x, minute_y, minute_thickness, BLACK)
fill_disk(cx, cy, center_radius, BLACK)
end
draw_circle(face_radius, face_stroke, BLACK)
draw_ticks()
draw_hands()
local file = assert(io.open(output_path, "wb"))
file:write(string.format("P5\n%d %d\n255\n", width, height))
local char_cache = {
[WHITE] = string.char(WHITE),
[BLACK] = string.char(BLACK),
}
for y = 0, height - 1 do
local row = {}
for x = 0, width - 1 do
local value = pixels[pixel_index(x, y)]
row[#row + 1] = char_cache[value] or string.char(value)
end
file:write(table.concat(row))
end
file:close()

View File

@@ -2,31 +2,60 @@
set -eu
DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
clock_face_path=${CLOCK_FACE_PATH:-"$DIR/../assets/clock-face.png"}
hour_assets_dir=${CLOCK_HOUR_ASSETS_DIR:-"$DIR/../assets/hour-hand"}
minute_assets_dir=${CLOCK_MINUTE_ASSETS_DIR:-"$DIR/../assets/minute-hand"}
clock_region_x=${CLOCK_REGION_X:-313}
clock_region_y=${CLOCK_REGION_Y:-0}
ENV_FILE="$DIR/env.sh"
# 单独执行本脚本时,也需要读取同一份坐标配置。
# 否则会退回到脚本内默认值,导致手工调试与主循环绘制位置不一致。
# shellcheck disable=SC1090
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
clock_region_x=${CLOCK_REGION_X:-262}
clock_region_y=${CLOCK_REGION_Y:-55}
clock_region_width=${CLOCK_REGION_WIDTH:-220}
clock_region_height=${CLOCK_REGION_HEIGHT:-220}
clock_face_radius_ratio=${CLOCK_FACE_RADIUS_RATIO:-0.47}
clock_face_stroke=${CLOCK_FACE_STROKE:-3}
clock_tick_outer_inset=${CLOCK_TICK_OUTER_INSET:-6}
clock_major_tick_length=${CLOCK_MAJOR_TICK_LENGTH:-14}
clock_minor_tick_length=${CLOCK_MINOR_TICK_LENGTH:-7}
clock_major_tick_thickness=${CLOCK_MAJOR_TICK_THICKNESS:-4}
clock_minor_tick_thickness=${CLOCK_MINOR_TICK_THICKNESS:-2}
clock_hour_length_ratio=${CLOCK_HOUR_LENGTH_RATIO:-0.48}
clock_minute_length_ratio=${CLOCK_MINUTE_LENGTH_RATIO:-0.72}
clock_hour_thickness=${CLOCK_HOUR_THICKNESS:-9}
clock_minute_thickness=${CLOCK_MINUTE_THICKNESS:-5}
clock_center_radius=${CLOCK_CENTER_RADIUS:-7}
force_full_refresh=${1:-false}
output_path="$DIR/state/clock-render.pgm"
eval "$("$DIR/clock-index.sh")"
hour_patch="$hour_assets_dir/$hour_index.png"
minute_patch="$minute_assets_dir/$minute_index.png"
mkdir -p "$DIR/state"
if [ ! -f "$clock_face_path" ] || [ ! -f "$hour_patch" ] || [ ! -f "$minute_patch" ]; then
echo "Clock assets missing."
echo "Face: $clock_face_path"
echo "Hour: $hour_patch"
echo "Minute: $minute_patch"
exit 1
fi
lua "$DIR/render-clock.lua" \
"$output_path" \
"$clock_region_width" \
"$clock_region_height" \
"$hour" \
"$minute" \
"$clock_face_radius_ratio" \
"$clock_face_stroke" \
"$clock_tick_outer_inset" \
"$clock_major_tick_length" \
"$clock_minor_tick_length" \
"$clock_major_tick_thickness" \
"$clock_minor_tick_thickness" \
"$clock_hour_length_ratio" \
"$clock_minute_length_ratio" \
"$clock_hour_thickness" \
"$clock_minute_thickness" \
"$clock_center_radius"
if [ "$force_full_refresh" = true ]; then
/usr/sbin/eips -f -g "$clock_face_path" -x "$clock_region_x" -y "$clock_region_y"
# Kindle Voyage 当前这条链路里fbink 默认会叠加 viewport 修正,
# 导致图像在屏幕上出现双重偏移。这里强制关闭 viewport 修正,
# 让坐标与网页导出的像素坐标保持一致。
fbink -q -V -f -g "file=$output_path,x=$clock_region_x,y=$clock_region_y"
else
/usr/sbin/eips -g "$clock_face_path" -x "$clock_region_x" -y "$clock_region_y"
fbink -q -V -g "file=$output_path,x=$clock_region_x,y=$clock_region_y"
fi
/usr/sbin/eips -g "$hour_patch" -x "$clock_region_x" -y "$clock_region_y"
/usr/sbin/eips -g "$minute_patch" -x "$clock_region_x" -y "$clock_region_y"

View File

@@ -14,5 +14,7 @@ mkdir -p "$(dirname "$LOG_FILE")"
if [ "$DEBUG" = true ]; then
"$DIR/dash.sh"
else
"$DIR/dash.sh" >>"$LOG_FILE" 2>&1 &
# 通过 SSH 或 KUAL 触发时,父 shell 很快就会退出。
# 这里必须用 nohup 脱离会话,否则后台的 dash.sh 会跟着收到 HUP 退出。
nohup "$DIR/dash.sh" >>"$LOG_FILE" 2>&1 </dev/null &
fi

View File

@@ -1,2 +1,56 @@
#!/usr/bin/env sh
pkill -f dash.sh
set -eu
# 退出 dashboard 时,不能只“启动一下 framework”就结束。
# Voyage 5.13.6 上白屏往往来自 framework / webreader / cvm 半恢复状态:
# 进程看起来在,但前台 Java UI 实际没起来。
# 这里统一做一次干净的 UI 栈重启,尽量把设备拉回正常首页/KUAL 可用状态。
stop_job() {
job_name=$1
stop "$job_name" >/dev/null 2>&1 || initctl stop "$job_name" >/dev/null 2>&1 || true
}
start_job() {
job_name=$1
start "$job_name" >/dev/null 2>&1 || initctl start "$job_name" >/dev/null 2>&1 || true
}
wait_for_cvm() {
attempts=0
while [ "$attempts" -lt 12 ]; do
if pgrep -x cvm >/dev/null 2>&1; then
return 0
fi
attempts=$((attempts + 1))
sleep 1
done
return 1
}
pkill -f dash.sh 2>/dev/null || true
pkill -f start.sh 2>/dev/null || true
lipc-set-prop com.lab126.powerd preventScreenSaver 0 2>/dev/null || true
# 先把残留 UI 栈彻底停干净,避免 webreader 存活但 cvm 已崩的白屏状态。
stop_job webreader
stop_job framework
killall cvm 2>/dev/null || true
sleep 2
if [ -x /etc/init.d/framework ]; then
/etc/init.d/framework start || true
else
start_job framework
fi
# framework 拉起后,先等 cvm 真正起来,再启动 webreader。
if ! wait_for_cvm; then
echo "警告framework 已请求启动,但 cvm 未在预期时间内出现。"
fi
start_job webreader
sleep 2