Skip to content

Commit 85879de

Browse files
author
Vova
committed
Initial release v0.3.alfa
0 parents  commit 85879de

25 files changed

Lines changed: 38406 additions & 0 deletions

.github/workflows/release.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Create release
19+
uses: softprops/action-gh-release@v2
20+
with:
21+
generate_release_notes: true
22+
files: |
23+
dist/index.js
24+
main.py
25+
package.json
26+
plugin.json
27+
LICENSE
28+
README.md
29+
fail_on_unmatched_files: false
30+
31+
- name: Create plugin zip
32+
run: |
33+
zip -r eGPUBridge-${{ github.ref_name }}.zip \
34+
dist/ \
35+
src/ \
36+
main.py \
37+
package.json \
38+
plugin.json \
39+
bin/ \
40+
LICENSE \
41+
README.md
42+
43+
- name: Upload zip to release
44+
uses: softprops/action-gh-release@v2
45+
with:
46+
tag_name: ${{ github.ref_name }}
47+
files: eGPUBridge-${{ github.ref_name }}.zip

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
__pycache__/
2+
*.pyc
3+
*.pyo
4+
*.log
5+
*.log.*
6+
*.json.bak
7+
.stable_backup/
8+
backup_*/
9+
.claude/
10+
*.conf
11+
last_status.json
12+
hotkey_settings.json
13+
tv_control_automation.json
14+
.DS_Store
15+
node_modules/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Vova + GPT
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# eGPUBridge
2+
3+
![Version](https://img.shields.io/badge/version-0.3.alfa-blue)
4+
![Decky](https://img.shields.io/badge/Decky-Loader-green)
5+
![SteamOS](https://img.shields.io/badge/SteamOS-3.x-orange)
6+
![License](https://img.shields.io/badge/license-MIT-green)
7+
8+
**eGPU manager for SteamOS Game Mode** — seamless eGPU switching, TV control, and GPU tuning for handheld gaming PCs.
9+
10+
## Features
11+
12+
- **SMART Display Switch** — one-tap toggle between internal and external display
13+
- **GPU Tuning** — power cap, fan control, performance level, overclocking (AMD)
14+
- **TV Control** — ADB-based TV power/input control with Wi-Fi auto-start
15+
- **NVIDIA Support** — DKMS driver install, activate/deactivate, nvidia-smi telemetry
16+
- **Dock Detection** — USB4/Thunderbolt dock status, ASMedia 246x identification
17+
- **Safe Disconnect** — graceful eGPU removal with PCI cleanup
18+
- **Gamepad UI** — fully navigable with Steam Deck gamepad controls
19+
- **Recovery Hotkeys** — hardware button combos for display recovery
20+
21+
## Supported Devices
22+
23+
| Device | Status |
24+
|--------|--------|
25+
| Steam Deck / OLED | Full support |
26+
| ASUS ROG Ally / Ally X | Full support |
27+
| Lenovo Legion Go | Full support |
28+
| Lenovo Legion Go S | Full support (eGPU detected) |
29+
30+
## Installation
31+
32+
### Via Decky Loader (recommended)
33+
34+
1. Install [Decky Loader](https://decky.xyz) on your Steam Deck
35+
2. Open Decky in Game Mode → Plugin Store
36+
3. Search for "eGPUBridge" → Install
37+
38+
### Manual Install
39+
40+
1. Download the latest release from [Releases](https://github.com/WowOne987/eGPUBridge/releases)
41+
2. Copy the plugin folder to `~/homebrew/plugins/eGPUBridge/`
42+
3. Restart Decky: `sudo systemctl restart plugin_loader`
43+
44+
> **Note:** Plugin requires `root` flag — Decky will prompt for sudo access.
45+
46+
## Usage
47+
48+
### SMART Button
49+
The main control — toggles between internal display and eGPU-connected external display. Shows current connector name (e.g., "HDMI 1 TV").
50+
51+
### TV Control
52+
- **ON / HDMI / OFF** — control TV power and input via ADB
53+
- **Wi-Fi Auto Start** — automatically switch TV input when eGPU is detected
54+
- **IP Roller** — gamepad-friendly IP address input for TV
55+
56+
### GPU Tuning (AMD)
57+
- **Power Limit** — adjust GPU power cap (D-pad left/right)
58+
- **Performance Level** — AUTO / HIGH / LOW / MANUAL
59+
- **Power Profile** — BOOTUP / 3D_FULL_SCREEN / POWER_SAVING / etc.
60+
- **Manual Clocks** — GPU/VRAM/Voltage sliders (MANUAL mode)
61+
62+
### GPU Tuning (NVIDIA)
63+
- **Power Cap** — via `nvidia-smi -pl`
64+
- **Fan Control** — auto/manual via `nvidia-settings`
65+
- **Performance Level** — GPUPowerMizerMode (auto/high/low)
66+
67+
### NVIDIA Driver Management
68+
- **Install Driver** — DKMS-based nvidia-dkms installation on SteamOS
69+
- **Activate / Deactivate** — module loading, environment variables, gamescope restart
70+
- **Uninstall Driver** — clean removal with DKMS + pacman
71+
72+
### Other
73+
- **Recovery Hotkey** — toggle hardware button combos for display recovery
74+
- **Safe Unplug** — graceful eGPU disconnect
75+
- **Restore Internal** — switch back to internal display
76+
- **Diagnostics** — collect device info, TV health, recent events
77+
78+
## Building from Source
79+
80+
The plugin uses a pre-built frontend — no build step required.
81+
82+
```bash
83+
# The dist/index.js IS the source (no TypeScript compilation)
84+
# Edit dist/index.js directly for UI changes
85+
# Edit main.py for backend changes
86+
87+
# Verify syntax
88+
node -c dist/index.js
89+
python3 -c "import ast; ast.parse(open('main.py').read())"
90+
```
91+
92+
## Architecture
93+
94+
```
95+
eGPUBridge/
96+
├── dist/index.js # Frontend (React via Decky API)
97+
├── src/index.tsx # Source copy (identical to dist)
98+
├── main.py # Backend (Python, Decky Plugin class)
99+
├── package.json # Decky plugin metadata
100+
├── plugin.json # Decky plugin config
101+
├── bin/ # Shell scripts (auto-detect, shutdown)
102+
│ ├── egpubridge-auto.sh
103+
│ ├── egpubridge-shutdown.sh
104+
│ ├── gamescope-session-egpubridge
105+
│ └── platform-tools/ # ADB, fastboot
106+
└── LICENSE
107+
```
108+
109+
## Contributing
110+
111+
1. Fork the repository
112+
2. Create a feature branch
113+
3. Make your changes (edit `dist/index.js` and/or `main.py`)
114+
4. Test on Steam Deck or compatible device
115+
5. Submit a Pull Request
116+
117+
## License
118+
119+
[MIT](LICENSE) — Copyright (c) 2026 Vova + GPT

bin/adb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/home/deck/homebrew/plugins/eGPUBridge/bin/platform-tools/adb

bin/egb-copy-report

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/bin/sh
2+
BASE="/home/deck/homebrew/plugins/eGPUBridge"
3+
OUT="/tmp/egpubridge-copy-report.txt"
4+
5+
{
6+
echo "===== COPY START: eGPUBridge report ====="
7+
echo "Time: $(date)"
8+
echo
9+
10+
echo "===== 1) CONFIG ====="
11+
echo "--- output_order.conf ---"
12+
cat "$BASE/output_order.conf" 2>/dev/null || echo "missing"
13+
echo
14+
echo "--- prefer_vk_device.conf ---"
15+
cat "$BASE/prefer_vk_device.conf" 2>/dev/null || echo "missing"
16+
echo
17+
echo "--- gamescope_mode.conf ---"
18+
cat "$BASE/gamescope_mode.conf" 2>/dev/null || echo "missing"
19+
echo
20+
21+
echo "===== 2) GAMESCOPE ====="
22+
pgrep -a gamescope || true
23+
echo
24+
25+
echo "===== 3) DISPLAY / GPU STATUS ====="
26+
python3 - <<'PY'
27+
import sys, json
28+
sys.path.insert(0, "/home/deck/homebrew/plugins/eGPUBridge")
29+
try:
30+
import main
31+
st = main.build_status(heavy=False)
32+
print(json.dumps({
33+
"ok": st.get("ok"),
34+
"version": st.get("version"),
35+
"connected": st.get("connected"),
36+
"display_target": st.get("display_target"),
37+
"gpu_label": st.get("gpu_label"),
38+
"display_label": st.get("display_label"),
39+
"current_mode": st.get("current_mode"),
40+
"internal_display": st.get("internal_display"),
41+
"external_display": st.get("external_display"),
42+
"tv_network": st.get("tv_network"),
43+
"tv_power": st.get("tv_power"),
44+
}, indent=2, ensure_ascii=False, default=str))
45+
except Exception as e:
46+
print("ERROR:", repr(e))
47+
PY
48+
echo
49+
50+
echo "===== 4) TV CONTROL HEALTH ====="
51+
python3 - <<'PY'
52+
import sys, json, asyncio
53+
sys.path.insert(0, "/home/deck/homebrew/plugins/eGPUBridge")
54+
try:
55+
import main
56+
async def run():
57+
res = await main.Plugin.tv_control_health()
58+
print(json.dumps({
59+
"ok": res.get("ok"),
60+
"label": res.get("label"),
61+
"network": res.get("network"),
62+
"adb": res.get("adb"),
63+
"wol": res.get("wol"),
64+
"buttons": res.get("buttons"),
65+
"missing": res.get("missing"),
66+
}, indent=2, ensure_ascii=False, default=str))
67+
asyncio.run(run())
68+
except Exception as e:
69+
print("ERROR:", repr(e))
70+
PY
71+
echo
72+
73+
echo "===== 5) ADB DEVICES ====="
74+
if [ -x "$BASE/bin/adb" ]; then
75+
"$BASE/bin/adb" devices -l || true
76+
else
77+
echo "local adb missing"
78+
fi
79+
echo
80+
81+
echo "===== 6) UI / TV CONTROL MARKERS ====="
82+
grep -nE 'TV ON|HDMI|TV OFF|tv_on|tv_input|tv_off|TV Control BETA|Check TV Control status|FRONTEND_CLICK_TV_CONTROL|doCall' "$BASE/src/index.tsx" 2>/dev/null | tail -n 80 || true
83+
echo
84+
85+
echo "===== 7) BACKEND METHOD SIGNATURES ====="
86+
python3 - <<'PY'
87+
import sys, inspect, json
88+
sys.path.insert(0, "/home/deck/homebrew/plugins/eGPUBridge")
89+
try:
90+
import main
91+
names = [
92+
"tv_on", "tv_input", "tv_off",
93+
"tv_input_mode", "tv_control_health",
94+
"smart_toggle_display", "apply_egpu_mode", "restore_internal_mode",
95+
]
96+
out = {}
97+
for name in names:
98+
obj = getattr(main.Plugin, name, None)
99+
if obj is None:
100+
out[name] = {"exists": False}
101+
continue
102+
try:
103+
sig = str(inspect.signature(obj))
104+
except Exception as e:
105+
sig = "signature_error: " + str(e)
106+
out[name] = {
107+
"exists": True,
108+
"signature": sig,
109+
"is_coroutine": inspect.iscoroutinefunction(obj),
110+
"type": str(type(obj)),
111+
}
112+
print(json.dumps(out, indent=2, ensure_ascii=False))
113+
except Exception as e:
114+
print("ERROR:", repr(e))
115+
PY
116+
echo
117+
118+
echo "===== 8) RECENT USEFUL PLUGIN LOGS ====="
119+
grep -Ei 'UI_CALL|FRONTEND|tv_on|tv_input|tv_off|tv_control|KEYCODE|HDMI_SWITCH|python_wol|adb|restore_internal|apply_egpu|RUN CLEAN|STATUS_SUMMARY' "$BASE/plugin.log" 2>/dev/null | tail -n 100 || true
120+
echo
121+
122+
echo "===== 9) LATEST KNOWN GOOD BACKUPS ====="
123+
ls -1dt "$BASE"/stable_backup/KNOWN_GOOD_* 2>/dev/null | head -n 12 || true
124+
echo
125+
ls -1t "$BASE"/stable_backup/rollback_known_good_* 2>/dev/null | head -n 12 || true
126+
echo
127+
128+
echo "===== COPY END: eGPUBridge report ====="
129+
} > "$OUT"
130+
131+
cat "$OUT"
132+
echo
133+
echo "Report file saved to: $OUT"

bin/egpubridge-auto.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
# eGPUBridge: Auto-detect and activate eGPU at boot
3+
VENDOR_FILE="/home/deck/.config/egpubridge/vendor"
4+
LOG="/home/deck/homebrew/plugins/eGPUBridge/auto.log"
5+
6+
echo "$(date) eGPUBridge auto: starting" >> "$LOG"
7+
8+
vendor=$(cat "$VENDOR_FILE" 2>/dev/null || echo "auto")
9+
10+
# Wait for Thunderbolt devices to enumerate
11+
sleep 5
12+
13+
# Check for NVIDIA eGPU
14+
nvidia_found=false
15+
for dev in /sys/bus/pci/devices/*; do
16+
v=$(cat "$dev/vendor" 2>/dev/null)
17+
boot=$(cat "$dev/boot_vga" 2>/dev/null)
18+
if [ "$v" = "0x10de" ] && [ "$boot" != "1" ]; then
19+
nvidia_found=true
20+
echo "$(date) eGPUBridge auto: NVIDIA device found at $(basename $dev)" >> "$LOG"
21+
break
22+
fi
23+
done
24+
25+
if [ "$nvidia_found" = "true" ]; then
26+
# Load nvidia modules
27+
echo "$(date) eGPUBridge auto: loading nvidia modules" >> "$LOG"
28+
modprobe nvidia 2>> "$LOG"
29+
modprobe nvidia-uvm 2>> "$LOG"
30+
modprobe nvidia-drm modeset=1 2>> "$LOG"
31+
32+
# Verify
33+
if nvidia-smi -L 2>/dev/null; then
34+
echo "$(date) eGPUBridge auto: nvidia-smi OK" >> "$LOG"
35+
else
36+
echo "$(date) eGPUBridge auto: nvidia-smi failed" >> "$LOG"
37+
fi
38+
fi
39+
40+
echo "$(date) eGPUBridge auto: done" >> "$LOG"

0 commit comments

Comments
 (0)