feat: M6.43 — reader分段流式读取(Phase1,只txt) + 面板前台管理/返回回桌面按钮
- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮
0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
This commit is contained in:
1 parent
a64ae06eb6
commit
f40abbd494
17 files changed
+1069
-70
No files matched your search
@@ -39,7 +39,9 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
power: '<svg viewBox="0 0 24 24"><path d="M12 3v8" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M6.6 6.6a7.5 7.5 0 1 0 10.8 0" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
|
||||
play: '<svg viewBox="0 0 24 24"><path d="M7 5.5v13l11-6.5z" fill="currentColor"/></svg>',
|
||||
next: '<svg viewBox="0 0 24 24"><path d="M5 5.5l9 6.5-9 6.5z" fill="currentColor"/><rect x="16.2" y="5" width="2.4" height="14" fill="currentColor"/></svg>',
|
||||
prev: '<svg viewBox="0 0 24 24"><path d="M19 5.5l-9 6.5 9 6.5z" fill="currentColor"/><rect x="5.4" y="5" width="2.4" height="14" fill="currentColor"/></svg>'
|
||||
prev: '<svg viewBox="0 0 24 24"><path d="M19 5.5l-9 6.5 9 6.5z" fill="currentColor"/><rect x="5.4" y="5" width="2.4" height="14" fill="currentColor"/></svg>',
|
||||
back: '<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" fill="currentColor"/></svg>',
|
||||
home: '<svg viewBox="0 0 24 24"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" fill="currentColor"/></svg>'
|
||||
};
|
||||
let ctlBright = { min: 1, max: 100, value: 100 }; // 控制板亮度(读取自 /api/settings/display.brightness)
|
||||
let logsGroup = 'all', logsPrefix = '';
|
||||
@@ -889,9 +891,10 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- 批④ 后台活动页(#/bg):列表 + 行内「杀死」 + 顶栏「一键清后台」(复用 /api/ams/bg 只读 + /api/ams/:pkg/kill) ---- */
|
||||
/* ---- 批④ App 活动页(#/bg):前台「终止」救命 + 后台列表/行内「杀死」 + 顶栏「一键清后台」
|
||||
*(复用 /api/ams/bg 只读:apps=bg 向后兼容 + fg=前台;前台终止走 /api/ams/:pkg/killfg,后台杀死走 /api/ams/:pkg/kill) ---- */
|
||||
let bgTimer = null;
|
||||
let bgData = null; // {available,count,apps:[{pkg,name,pid,state,cpu_pct,rss_kb}]}
|
||||
let bgData = null; // {available,count,apps:[{pkg,name,pid,state,is_fg,cpu_pct,rss_kb}],fg_count,fg:[...]}
|
||||
|
||||
function stopBg() { if (bgTimer) { clearInterval(bgTimer); bgTimer = null; } }
|
||||
|
||||
@@ -900,30 +903,43 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
return '<span class="bg-ico">' + ch + '</span>';
|
||||
}
|
||||
|
||||
/* 状态徽标:前台=ok/绿,后台=warn/黄(复用已定义的 home-chip 组件样式)。 */
|
||||
function bgStateBadge(a) {
|
||||
const isFg = a.is_fg || a.state === 'fg';
|
||||
return '<span class="home-chip ' + (isFg ? 'ok' : 'warn') + '">' + (isFg ? '前台' : '后台') + '</span>';
|
||||
}
|
||||
|
||||
/* 单行 App 卡(kind='fg'=前台可终止/救命;'bg'=后台可杀死)。 */
|
||||
function bgRowHtml(a, kind) {
|
||||
const cpu = (a.cpu_pct == null) ? '—' : a.cpu_pct.toFixed(1) + '%';
|
||||
const mem = (a.rss_kb == null) ? '—' : fmtBytes(Number(a.rss_kb) * 1024);
|
||||
const sub = 'CPU ' + cpu + ' · ' + mem +
|
||||
(a.pid > 0 ? ' · pid ' + esc(a.pid) : '') + ' · ' + bgStateBadge(a);
|
||||
const btn = (kind === 'fg')
|
||||
? '<button class="btn btn-danger" data-bgkillfg="' + esc(a.pkg) + '">终止</button>'
|
||||
: '<button class="btn btn-danger" data-bgkill="' + esc(a.pkg) + '">杀死</button>';
|
||||
return '<div class="bg-row">' + bgIconHtml(a) +
|
||||
'<div class="bg-info"><div class="bg-name">' + esc(a.name || a.pkg || '未命名') + '</div>' +
|
||||
'<div class="bg-sub">' + sub + '<span class="mono">' + esc(a.pkg) + '</span></div></div>' +
|
||||
btn + '</div>';
|
||||
}
|
||||
|
||||
function renderBgBody(d) {
|
||||
let h = '<div class="page" id="bg-page">';
|
||||
h += '<div class="card"><h3 class="card-title">后台活动 App(meow_amsd bg · 只读)</h3>';
|
||||
h += '<div class="card"><h3 class="card-title">App 活动(前台可终止 · 后台管理)</h3>';
|
||||
if (!d || d.available === false) {
|
||||
h += homeUnavailable();
|
||||
} else {
|
||||
const fg = d.fg || [];
|
||||
const apps = d.apps || [];
|
||||
if (!apps.length) {
|
||||
h += '<div class="empty">无后台 App</div>';
|
||||
} else {
|
||||
h += '<div class="bg-list">';
|
||||
apps.forEach(a => {
|
||||
const cpu = (a.cpu_pct == null) ? '—' : a.cpu_pct.toFixed(1) + '%';
|
||||
const mem = (a.rss_kb == null) ? '—' : fmtBytes(Number(a.rss_kb) * 1024);
|
||||
const sub = 'CPU ' + cpu + ' · ' + mem + ' · 后台' +
|
||||
(a.pid > 0 ? ' · pid ' + esc(a.pid) : '');
|
||||
h += '<div class="bg-row">' + bgIconHtml(a) +
|
||||
'<div class="bg-info"><div class="bg-name">' + esc(a.name || a.pkg || '未命名') + '</div>' +
|
||||
'<div class="bg-sub">' + sub + '<span class="mono">' + esc(a.pkg) + '</span></div></div>' +
|
||||
'<button class="btn btn-danger" data-bgkill="' + esc(a.pkg) + '">杀死</button></div>';
|
||||
});
|
||||
h += '</div>';
|
||||
}
|
||||
h += '<p class="muted">占用数据读自 /proc/<pid>(与启动器后台管理页同源、同口径);「杀死」经 amsd(require_auth)。CPU 首采样为「—」,轮询后显示真实占用;数据不可得时如实显示「—」,绝不编造。</p>';
|
||||
h += '<p class="muted">前台 App 可「终止」(救命:前台卡死/无响应时强制结束,需二次确认);后台 App 可「杀死」或一键清。</p>';
|
||||
h += '<div class="bg-group">' + (fg.length ? '前台 App(' + fg.length + ')' : '前台 App') + '</div>';
|
||||
if (!fg.length) h += '<div class="empty">当前无前台 App</div>';
|
||||
else { h += '<div class="bg-list">'; fg.forEach(a => { h += bgRowHtml(a, 'fg'); }); h += '</div>'; }
|
||||
h += '<div class="bg-group">' + (apps.length ? '后台 App(' + apps.length + ')' : '后台 App') + '</div>';
|
||||
if (!apps.length) h += '<div class="empty">无后台 App</div>';
|
||||
else { h += '<div class="bg-list">'; apps.forEach(a => { h += bgRowHtml(a, 'bg'); }); h += '</div>'; }
|
||||
h += '<p class="muted">占用数据读自 /proc/<pid>(与启动器后台管理页同源、同口径);「终止/杀死」经 amsd(require_auth),前台终止为救命操作需二次确认。CPU 首采样为「—」,轮询后显示真实占用;数据不可得时如实显示「—」,绝不编造。</p>';
|
||||
}
|
||||
h += '</div></div>';
|
||||
return h;
|
||||
@@ -965,6 +981,15 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
fetchBgOnce();
|
||||
}
|
||||
|
||||
/* 前台强制终止(救命):明确标注「前台」,二次确认;后端 am kill 优先、launcher 拒则直接按进程信号。 */
|
||||
async function bgKillFg(pkg) {
|
||||
const ok = await confirmDlg('终止前台 App', '『' + pkg + '』当前位于前台。若它已卡死/无响应,可强制终止(救命):先请求干净结束,启动器拒绝时直接按进程信号(SIGTERM/SIGKILL)。确认终止?', true);
|
||||
if (!ok) return;
|
||||
try { await api('POST', '/api/ams/' + encodeURIComponent(pkg) + '/killfg'); toast('已请求终止前台 ' + pkg); }
|
||||
catch (e) { toast('终止失败:' + errText(e)); }
|
||||
fetchBgOnce();
|
||||
}
|
||||
|
||||
async function bgClearAll() {
|
||||
const list = (bgData && bgData.apps) ? bgData.apps : [];
|
||||
if (!list.length) { toast('无后台 App 可清'); return; }
|
||||
@@ -981,6 +1006,8 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
|
||||
function bindBg() {
|
||||
$('#content').addEventListener('click', async function (e) {
|
||||
const kfg = e.target.closest('[data-bgkillfg]');
|
||||
if (kfg) { await bgKillFg(kfg.dataset.bgkillfg); return; }
|
||||
const kl = e.target.closest('[data-bgkill]');
|
||||
if (kl) { await bgKill(kl.dataset.bgkill); return; }
|
||||
});
|
||||
@@ -1967,7 +1994,17 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
else if (dir === 'voldown') sendKeyRepeat(114, volSteps(ay), '音量−');
|
||||
else if (dir === 'next') sendKeyLabel(163, '下一曲');
|
||||
else if (dir === 'prev') sendKeyLabel(165, '上一曲');
|
||||
else if (dir === 'back') sendKeyLabel(158, '返回');
|
||||
else if (dir === 'back') sendGesture('back'); // 左缘右滑 → 注入设备已识别的返回手势
|
||||
}
|
||||
|
||||
async function sendGesture(name) {
|
||||
const label = name === 'back' ? '返回' : '回桌面';
|
||||
try {
|
||||
const d = await api('POST', '/api/input/gesture', { gesture: name });
|
||||
if (d && d.dispatched) toast(label + ' 已触发');
|
||||
else if (d && d.reason === 'local_touch_active') toast('本机触摸中,注入已暂停');
|
||||
else toast(label + ' 触发未派发');
|
||||
} catch (e) { toast(label + ' 触发失败:' + errText(e)); }
|
||||
}
|
||||
|
||||
function remotePointerDown(e) {
|
||||
@@ -2010,6 +2047,8 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
h += remoteBtn('next', 'next', '下一曲', '下一曲(code=163)');
|
||||
h += remoteBtn('prev', 'prev', '上一曲', '上一曲(code=165)');
|
||||
h += remoteBtn('power', 'power', '电源', '电源键(单击;code=116)');
|
||||
h += remoteBtn('back', 'back', '返回', '左缘右滑返回(注入设备已识别的返回手势)');
|
||||
h += remoteBtn('home', 'home', '回桌面', '底部上滑回桌面(注入设备已识别的回桌面手势)');
|
||||
h += '</div>';
|
||||
h += '<div class="remote-bright">' +
|
||||
'<span class="r-lbl">指定亮度</span>' +
|
||||
@@ -2018,7 +2057,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
'</div>';
|
||||
h += '<button class="btn" id="ctl-powerlong">' + (MP_ICONS.power || '') + ' <span>长按电源(电源菜单)</span></button>';
|
||||
h += '<p class="muted" style="margin-top:8px;line-height:1.8">滑动手势(在遥控板上):上滑=音量+ · 下滑=音量- · 左滑=下一曲 · 右滑=上一曲 · 左缘右滑=返回。<br>' +
|
||||
'<b>返回/主页</b>属物理键红线(<code>/api/key</code> 后端禁注入 158/172),故未提供注入按钮。</p>';
|
||||
'<b>返回/回桌面</b>走手势注入(<code>/api/input/gesture</code>,模拟设备已识别的左缘右滑/底部上滑),<b>不走</b>被物理键红线禁注入的 <code>/api/key</code> 158/172。</p>';
|
||||
h += '</div>';
|
||||
return h;
|
||||
}
|
||||
@@ -2484,6 +2523,8 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
else if (a === 'next') sendKey(163, 'tap');
|
||||
else if (a === 'prev') sendKey(165, 'tap');
|
||||
else if (a === 'power') sendKey(116, 'tap');
|
||||
else if (a === 'back') sendGesture('back');
|
||||
else if (a === 'home') sendGesture('home');
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('#ctl-powerlong')) { sendKey(116, 'long', 3000); return; }
|
||||
|
||||
Reference in new issue
Block a user