2026-03-31 07:14:36 -07:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>Driver Manager</title>
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
--bg: #0d1117;
|
|
|
|
|
--surface: #161b22;
|
|
|
|
|
--surface2: #21262d;
|
|
|
|
|
--border: #30363d;
|
|
|
|
|
--primary: #58a6ff;
|
|
|
|
|
--primary-dim: #388bfd;
|
|
|
|
|
--green: #3fb950;
|
|
|
|
|
--orange: #d29922;
|
|
|
|
|
--red: #f85149;
|
|
|
|
|
--text: #c9d1d9;
|
|
|
|
|
--text-dim: #8b949e;
|
|
|
|
|
--radius: 10px;
|
|
|
|
|
}
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
body {
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
padding: 16px;
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
}
|
|
|
|
|
.header { text-align: center; padding: 20px 0 12px; }
|
|
|
|
|
.header h1 { font-size: 20px; color: var(--primary); }
|
|
|
|
|
.header .sub { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
padding: 14px;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
.card-title {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.8px;
|
|
|
|
|
color: var(--primary);
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
.card-title .dot {
|
|
|
|
|
width: 8px; height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: var(--green);
|
|
|
|
|
}
|
|
|
|
|
.card-title .dot.off { background: var(--red); }
|
|
|
|
|
.card-title .dot.warn { background: var(--orange); }
|
|
|
|
|
|
|
|
|
|
.row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.row:last-child { border-bottom: none; }
|
|
|
|
|
.row-label { font-size: 14px; font-weight: 500; }
|
|
|
|
|
.row-desc { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
|
|
|
|
|
.row-value { font-size: 13px; color: var(--green); font-family: monospace; }
|
|
|
|
|
|
|
|
|
|
select.sel {
|
|
|
|
|
background: var(--surface2);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
outline: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
.info-item {
|
|
|
|
|
background: var(--surface2);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
}
|
|
|
|
|
.info-item .lbl { font-size: 10px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.3px; }
|
|
|
|
|
.info-item .val { font-size: 13px; font-weight: 500; margin-top: 2px; font-family: monospace; }
|
|
|
|
|
|
|
|
|
|
.log-box {
|
|
|
|
|
background: var(--surface2);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
font-family: "Cascadia Code", "Fira Code", monospace;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
max-height: 180px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-row { display: flex; gap: 6px; margin-top: 8px; }
|
|
|
|
|
.btn {
|
|
|
|
|
flex: 1;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
background: var(--surface2);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
.btn:active { opacity: 0.7; }
|
|
|
|
|
.btn-primary { background: var(--primary-dim); color: #fff; border-color: var(--primary); }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="header">
|
|
|
|
|
<h1>Driver Manager</h1>
|
|
|
|
|
<div class="sub">GPU / WiFi / Bluetooth / SDR / Controllers</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Hardware Info -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot"></span> Hardware</div>
|
|
|
|
|
<div class="info-grid">
|
|
|
|
|
<div class="info-item"><div class="lbl">Device</div><div class="val" id="hwDevice">—</div></div>
|
|
|
|
|
<div class="info-item"><div class="lbl">SoC</div><div class="val" id="hwSoc">—</div></div>
|
|
|
|
|
<div class="info-item"><div class="lbl">GPU</div><div class="val" id="hwGpu">—</div></div>
|
|
|
|
|
<div class="info-item"><div class="lbl">WiFi</div><div class="val" id="hwWifi">—</div></div>
|
|
|
|
|
<div class="info-item"><div class="lbl">Bluetooth</div><div class="val" id="hwBt">—</div></div>
|
|
|
|
|
<div class="info-item"><div class="lbl">USB Devices</div><div class="val" id="hwUsb">—</div></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- GPU -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="gpuDot"></span> GPU — PowerVR</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Driver Mode</div>
|
|
|
|
|
<div class="row-desc">Vulkan 1.4 / OpenGL ES 3.x / OpenCL 3.0</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="gpuMode" onchange="setMode('gpu_mode', this.value)">
|
|
|
|
|
<option value="performance">Performance</option>
|
|
|
|
|
<option value="balanced">Balanced</option>
|
|
|
|
|
<option value="powersave">Power Save</option>
|
|
|
|
|
<option value="compute">Compute (OpenCL)</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">Vulkan Version</div></div>
|
|
|
|
|
<div class="row-value" id="gpuVulkan">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">GL Renderer</div></div>
|
|
|
|
|
<div class="row-value" id="gpuRenderer">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- WiFi -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="wifiDot"></span> WiFi — BCM4390</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Driver Mode</div>
|
|
|
|
|
<div class="row-desc">Nexmon: monitor + injection support</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="wifiMode" onchange="setMode('wifi_mode', this.value)">
|
|
|
|
|
<option value="standard">Standard</option>
|
2026-03-31 07:20:55 -07:00
|
|
|
<option value="monitor">Monitor (Nexmon)</option>
|
2026-03-31 07:14:36 -07:00
|
|
|
<option value="injection">Injection (Nexmon)</option>
|
2026-03-31 07:20:55 -07:00
|
|
|
<option value="restore">Restore Stock</option>
|
2026-03-31 07:14:36 -07:00
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">Interface</div></div>
|
|
|
|
|
<div class="row-value" id="wifiIface">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">Nexmon Firmware</div></div>
|
|
|
|
|
<div class="row-value" id="wifiNexmon">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Bluetooth -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="btDot"></span> Bluetooth — QCA</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Driver Mode</div>
|
|
|
|
|
<div class="row-desc">Standard / pentest (raw HCI, all profiles)</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="btMode" onchange="setMode('bt_mode', this.value)">
|
|
|
|
|
<option value="standard">Standard</option>
|
|
|
|
|
<option value="pentest">Pentest</option>
|
|
|
|
|
<option value="disabled">Disabled</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- SDR -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="sdrDot"></span> SDR Drivers</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Radio Mode</div>
|
|
|
|
|
<div class="row-desc">RTL-SDR v1-4 / HackRF / Airspy / LimeSDR</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="sdrMode" onchange="setMode('sdr_mode', this.value)">
|
|
|
|
|
<option value="sdr">SDR Scanner</option>
|
|
|
|
|
<option value="dvbt">DVB-T (Digital TV)</option>
|
|
|
|
|
<option value="hackrf">HackRF (TX/RX)</option>
|
|
|
|
|
<option value="off">Off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Decoder</div>
|
|
|
|
|
<div class="row-desc">Background signal decoder</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="decoderMode" onchange="setMode('decoder_mode', this.value)">
|
|
|
|
|
<option value="off">Off</option>
|
|
|
|
|
<option value="adsb">ADS-B (1090 MHz)</option>
|
|
|
|
|
<option value="fm">FM Radio</option>
|
|
|
|
|
<option value="spectrum">Spectrum Scan</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">USB SDR Devices</div></div>
|
|
|
|
|
<div class="row-value" id="sdrDevices">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-31 07:38:02 -07:00
|
|
|
<!-- RTL Mode Switcher -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="rtlDot"></span> RTL-SDR Mode</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Active Mode</div>
|
|
|
|
|
<div class="row-desc">Only one mode can use the dongle at a time</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="rtlMode" onchange="switchRtlMode(this.value)">
|
|
|
|
|
<option value="off">Off</option>
|
|
|
|
|
<option value="dvbt">DVB-T (Live TV)</option>
|
|
|
|
|
<option value="fm">FM Radio</option>
|
|
|
|
|
<option value="sdr">SDR Scanner (rtl_tcp)</option>
|
|
|
|
|
<option value="adsb">ADS-B Tracking</option>
|
|
|
|
|
<option value="spectrum">Spectrum Scan</option>
|
|
|
|
|
<option value="hackrf">HackRF TX/RX</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">Process Status</div></div>
|
|
|
|
|
<div class="row-value" id="rtlStatus">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- DVB-T / Kodi -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="kodiDot"></span> DVB-T Live TV / Kodi</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">DVB-T Frequency</div>
|
|
|
|
|
<div class="row-desc">Channel center frequency (Hz)</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="dvbtFreq" onchange="setConf('dvbt_freq', this.value)">
|
|
|
|
|
<option value="474000000">474 MHz (Ch 21)</option>
|
|
|
|
|
<option value="482000000">482 MHz (Ch 22)</option>
|
|
|
|
|
<option value="490000000">490 MHz (Ch 23)</option>
|
|
|
|
|
<option value="498000000">498 MHz (Ch 24)</option>
|
|
|
|
|
<option value="506000000">506 MHz (Ch 25)</option>
|
|
|
|
|
<option value="514000000">514 MHz (Ch 26)</option>
|
|
|
|
|
<option value="522000000">522 MHz (Ch 27)</option>
|
|
|
|
|
<option value="530000000">530 MHz (Ch 28)</option>
|
|
|
|
|
<option value="538000000">538 MHz (Ch 29)</option>
|
|
|
|
|
<option value="546000000">546 MHz (Ch 30)</option>
|
|
|
|
|
<option value="554000000">554 MHz (Ch 31)</option>
|
|
|
|
|
<option value="562000000">562 MHz (Ch 32)</option>
|
|
|
|
|
<option value="570000000">570 MHz (Ch 33)</option>
|
|
|
|
|
<option value="578000000">578 MHz (Ch 34)</option>
|
|
|
|
|
<option value="586000000">586 MHz (Ch 35)</option>
|
|
|
|
|
<option value="594000000">594 MHz (Ch 36)</option>
|
|
|
|
|
<option value="602000000">602 MHz (Ch 37)</option>
|
|
|
|
|
<option value="610000000">610 MHz (Ch 38)</option>
|
|
|
|
|
<option value="618000000">618 MHz (Ch 39)</option>
|
|
|
|
|
<option value="626000000">626 MHz (Ch 40)</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Kodi Stream</div>
|
|
|
|
|
<div class="row-desc">Add to Kodi PVR IPTV Simple Client</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row-value" id="kodiUrl">http://127.0.0.1:8554</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="btn-row">
|
|
|
|
|
<button class="btn" onclick="exec('sh /data/adb/modules/driver-manager/scripts/kodi_dvbt_setup.sh setup'); log('Kodi DVB-T setup started')">Setup Kodi</button>
|
|
|
|
|
<button class="btn" onclick="exec('sh /data/adb/modules/driver-manager/scripts/kodi_dvbt_setup.sh scan'); log('Channel scan started')">Scan Channels</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- FM Radio -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="fmDot"></span> FM Radio</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Frequency (MHz)</div>
|
|
|
|
|
<div class="row-desc">FM broadcast frequency</div>
|
|
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="fmFreq" onchange="setConf('fm_freq', this.value)">
|
|
|
|
|
<option value="88100000">88.1</option>
|
|
|
|
|
<option value="89900000">89.9</option>
|
|
|
|
|
<option value="91500000">91.5</option>
|
|
|
|
|
<option value="93100000">93.1</option>
|
|
|
|
|
<option value="95500000">95.5</option>
|
|
|
|
|
<option value="97100000">97.1</option>
|
|
|
|
|
<option value="98500000">98.5</option>
|
|
|
|
|
<option value="100000000">100.0</option>
|
|
|
|
|
<option value="101500000">101.5</option>
|
|
|
|
|
<option value="103100000">103.1</option>
|
|
|
|
|
<option value="104700000">104.7</option>
|
|
|
|
|
<option value="106300000">106.3</option>
|
|
|
|
|
<option value="107900000">107.9</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-31 07:14:36 -07:00
|
|
|
<!-- Game Controllers -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title"><span class="dot" id="padDot"></span> Game Controllers</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="row-label">Controller Mode</div>
|
2026-03-31 07:20:55 -07:00
|
|
|
<div class="row-desc">All native: Xbox, PS5, PS4, Switch, Steam, Logitech, 8BitDo, Wacom</div>
|
2026-03-31 07:14:36 -07:00
|
|
|
</div>
|
|
|
|
|
<select class="sel" id="gamepadMode" onchange="setMode('gamepad_mode', this.value)">
|
|
|
|
|
<option value="auto">Auto (All)</option>
|
|
|
|
|
<option value="xbox">Xbox Only</option>
|
|
|
|
|
<option value="playstation">PlayStation Only</option>
|
|
|
|
|
<option value="nintendo">Nintendo Only</option>
|
|
|
|
|
<option value="off">Off</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div><div class="row-label">Connected Controllers</div></div>
|
|
|
|
|
<div class="row-value" id="padDevices">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Log -->
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-title">Log</div>
|
|
|
|
|
<div class="log-box" id="logArea">Initializing...</div>
|
|
|
|
|
<div class="btn-row">
|
|
|
|
|
<button class="btn" onclick="refreshAll()">Refresh</button>
|
|
|
|
|
<button class="btn" onclick="showLog()">Module Log</button>
|
|
|
|
|
<button class="btn btn-primary" onclick="applyAll()">Apply & Restart</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
const MODDIR = '/data/adb/modules/driver-manager';
|
|
|
|
|
|
|
|
|
|
function exec(cmd) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const cb = 'cb_' + Date.now() + '_' + Math.random().toString(36).slice(2);
|
|
|
|
|
window[cb] = (errno, stdout, stderr) => {
|
|
|
|
|
delete window[cb];
|
|
|
|
|
resolve({ errno, stdout: stdout || '', stderr: stderr || '' });
|
|
|
|
|
};
|
|
|
|
|
try { ksu.exec(cmd, '{}', cb); }
|
|
|
|
|
catch (e) { delete window[cb]; reject(e); }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function log(msg) {
|
|
|
|
|
const a = document.getElementById('logArea');
|
|
|
|
|
const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
|
|
|
a.textContent += '\n[' + ts + '] ' + msg;
|
|
|
|
|
a.scrollTop = a.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function gp(name) {
|
|
|
|
|
try { return (await exec('getprop ' + name)).stdout.trim(); }
|
|
|
|
|
catch (e) { return ''; }
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 07:38:02 -07:00
|
|
|
async function switchRtlMode(mode) {
|
|
|
|
|
log('Switching RTL mode to: ' + mode);
|
|
|
|
|
await exec('sh ' + MODDIR + '/scripts/rtl_mode_switch.sh ' + mode);
|
|
|
|
|
log('RTL mode switched to: ' + mode);
|
|
|
|
|
await loadRtlStatus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function setConf(file, value) {
|
|
|
|
|
await exec('echo "' + value + '" > ' + MODDIR + '/config/' + file);
|
|
|
|
|
log('Set ' + file + ' = ' + value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadRtlStatus() {
|
|
|
|
|
const mode = (await exec('cat ' + MODDIR + '/config/rtl_mode 2>/dev/null')).stdout.trim();
|
|
|
|
|
if (mode) document.getElementById('rtlMode').value = mode;
|
|
|
|
|
|
|
|
|
|
const status = (await exec('sh ' + MODDIR + '/scripts/rtl_mode_switch.sh status 2>/dev/null')).stdout.trim();
|
|
|
|
|
document.getElementById('rtlStatus').textContent = status || 'off';
|
|
|
|
|
|
|
|
|
|
const dot = document.getElementById('rtlDot');
|
|
|
|
|
dot.className = 'dot' + (mode && mode !== 'off' ? '' : ' off');
|
|
|
|
|
|
|
|
|
|
const kodiDot = document.getElementById('kodiDot');
|
|
|
|
|
kodiDot.className = 'dot' + (mode === 'dvbt' ? '' : ' off');
|
|
|
|
|
|
|
|
|
|
const fmDot = document.getElementById('fmDot');
|
|
|
|
|
fmDot.className = 'dot' + (mode === 'fm' ? '' : ' off');
|
|
|
|
|
|
|
|
|
|
// Load saved frequencies
|
|
|
|
|
const dvbtFreq = (await exec('cat ' + MODDIR + '/config/dvbt_freq 2>/dev/null')).stdout.trim();
|
|
|
|
|
if (dvbtFreq) document.getElementById('dvbtFreq').value = dvbtFreq;
|
|
|
|
|
|
|
|
|
|
const fmFreq = (await exec('cat ' + MODDIR + '/config/fm_freq 2>/dev/null')).stdout.trim();
|
|
|
|
|
if (fmFreq) document.getElementById('fmFreq').value = fmFreq;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 07:14:36 -07:00
|
|
|
async function setMode(file, value) {
|
|
|
|
|
log('Setting ' + file + ' = ' + value);
|
|
|
|
|
await exec('echo "' + value + '" > ' + MODDIR + '/config/' + file);
|
|
|
|
|
log('OK. Changes apply on next boot or click Apply & Restart.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function applyAll() {
|
|
|
|
|
log('Restarting driver service...');
|
|
|
|
|
await exec('sh ' + MODDIR + '/service.sh &');
|
|
|
|
|
log('Service restarted. Some changes need reboot.');
|
|
|
|
|
setTimeout(refreshAll, 2000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadHardware() {
|
|
|
|
|
document.getElementById('hwDevice').textContent = await gp('ro.product.model');
|
|
|
|
|
document.getElementById('hwSoc').textContent = await gp('ro.soc.model');
|
|
|
|
|
document.getElementById('hwGpu').textContent = await gp('ro.hardware.egl');
|
|
|
|
|
|
|
|
|
|
const wifi = (await exec('ls /sys/module/ | grep -iE "^(bcmdhd|qca|wcn|ath|mt76)" | head -1')).stdout.trim();
|
|
|
|
|
document.getElementById('hwWifi').textContent = wifi || 'unknown';
|
|
|
|
|
|
|
|
|
|
const bt = (await exec('ls /sys/module/ | grep -iE "^(btqca|btusb|btbcm)" | head -1')).stdout.trim();
|
|
|
|
|
document.getElementById('hwBt').textContent = bt || 'unknown';
|
|
|
|
|
|
|
|
|
|
const usb = (await exec('lsusb 2>/dev/null | wc -l || echo 0')).stdout.trim();
|
|
|
|
|
document.getElementById('hwUsb').textContent = usb + ' devices';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadModes() {
|
|
|
|
|
const modes = ['gpu_mode', 'wifi_mode', 'bt_mode', 'sdr_mode', 'gamepad_mode', 'decoder_mode'];
|
|
|
|
|
const ids = ['gpuMode', 'wifiMode', 'btMode', 'sdrMode', 'gamepadMode', 'decoderMode'];
|
|
|
|
|
for (let i = 0; i < modes.length; i++) {
|
|
|
|
|
const val = (await exec('cat ' + MODDIR + '/config/' + modes[i] + ' 2>/dev/null')).stdout.trim();
|
|
|
|
|
if (val) document.getElementById(ids[i]).value = val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadGpuInfo() {
|
|
|
|
|
const renderer = await gp('debug.hwui.renderer');
|
|
|
|
|
document.getElementById('gpuRenderer').textContent = renderer || 'default';
|
|
|
|
|
|
|
|
|
|
const vulkan = await gp('ro.hardware.vulkan');
|
|
|
|
|
document.getElementById('gpuVulkan').textContent = vulkan ? vulkan + ' (1.4)' : 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadWifiInfo() {
|
|
|
|
|
const iface = (await exec('ip link show wlan0 2>/dev/null | head -1')).stdout.trim();
|
|
|
|
|
document.getElementById('wifiIface').textContent = iface ? 'wlan0 (up)' : 'wlan0';
|
|
|
|
|
|
|
|
|
|
const nexmon = (await exec('ls ' + MODDIR + '/firmware/fw_bcm4390_*.bin 2>/dev/null | wc -l')).stdout.trim();
|
|
|
|
|
document.getElementById('wifiNexmon').textContent = nexmon > 0 ? nexmon + ' firmware(s)' : 'not installed';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadSdrInfo() {
|
|
|
|
|
// Detect connected USB SDR devices by vendor:product ID
|
|
|
|
|
const usb = (await exec(
|
|
|
|
|
'cat /sys/bus/usb/devices/*/idVendor 2>/dev/null | while read v; do ' +
|
|
|
|
|
'dir=$(grep -rl "$v" /sys/bus/usb/devices/*/idVendor 2>/dev/null | head -1 | xargs dirname); ' +
|
|
|
|
|
'p=$(cat "$dir/idProduct" 2>/dev/null); ' +
|
|
|
|
|
'case "$v:$p" in ' +
|
|
|
|
|
'"0bda:2832"|"0bda:2838") echo "RTL-SDR";; ' +
|
|
|
|
|
'"1d50:6089") echo "HackRF";; ' +
|
|
|
|
|
'"1d50:60a1") echo "Airspy";; ' +
|
|
|
|
|
'"0403:6014"|"04b4:00f3") echo "LimeSDR";; ' +
|
|
|
|
|
'esac; done 2>/dev/null | sort -u | tr "\\n" ", "'
|
|
|
|
|
)).stdout.trim();
|
|
|
|
|
document.getElementById('sdrDevices').textContent = usb || 'none detected';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadControllerInfo() {
|
|
|
|
|
const pads = (await exec('cat /proc/bus/input/devices 2>/dev/null | grep -iE "(xbox|playstation|dualsense|pro controller|8bitdo|gamepad)" | wc -l')).stdout.trim();
|
|
|
|
|
document.getElementById('padDevices').textContent = (pads && pads !== '0') ? pads + ' connected' : 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function showLog() {
|
|
|
|
|
const r = await exec('cat ' + MODDIR + '/driver-manager.log 2>/dev/null || echo "No log"');
|
|
|
|
|
document.getElementById('logArea').textContent = '=== Module Log ===\n' + r.stdout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshAll() {
|
|
|
|
|
log('Refreshing...');
|
|
|
|
|
await loadHardware();
|
|
|
|
|
await loadModes();
|
|
|
|
|
await loadGpuInfo();
|
|
|
|
|
await loadWifiInfo();
|
|
|
|
|
await loadSdrInfo();
|
|
|
|
|
await loadControllerInfo();
|
2026-03-31 07:38:02 -07:00
|
|
|
await loadRtlStatus();
|
2026-03-31 07:14:36 -07:00
|
|
|
log('Done');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
|
try { await refreshAll(); log('Ready'); }
|
|
|
|
|
catch (e) { log('Init error: ' + e.message); }
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|