Spaces:
Paused
Paused
Upload 5 files
Browse files- README (42).md +41 -0
- app (15) (8).py +1111 -0
- gitattributes (28).txt +35 -0
- requirements (26).txt +5 -0
- start (11).sh +92 -0
README (42).md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Z Computer Mode Server
|
| 3 |
+
emoji: ๐ฅ๏ธ
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# THE Z AI โ Computer Mode Server
|
| 11 |
+
|
| 12 |
+
ุณูุฑูุฑ ุงูุชุญูู
ุจุงููู
ุจููุชุฑ ุนู ุจุนุฏ ูู
ูุฏ **Computer** ูู ุชุทุจูู THE Z AI.
|
| 13 |
+
|
| 14 |
+
## ู
ุง ููุนูู ูุฐุง ุงูุณูุฑูุฑ
|
| 15 |
+
|
| 16 |
+
- ูุดุบูู ุจูุฆุฉ ููููุณ ูุงู
ูุฉ (Ubuntu 22.04 + Fluxbox Desktop)
|
| 17 |
+
- ููุชุญ WebSocket ุนูู `/ws` ููุชุญูู
ุงููุงู
ู
|
| 18 |
+
- ูุฏุนู
: ููุทุงุช ุดุงุดุฉุ ุจุซ ู
ุจุงุดุฑุ ุฃูุงู
ุฑ ุชุฑู
ููุงูุ ู
ุงูุณุ ููุญุฉ ู
ูุงุชูุญุ ุญุงูุธุฉ
|
| 19 |
+
|
| 20 |
+
## WebSocket Actions
|
| 21 |
+
|
| 22 |
+
| ุงูุฃู
ุฑ | ุงููุตู |
|
| 23 |
+
|-------|--------|
|
| 24 |
+
| `screenshot` | ุงูุชูุงุท ููุทุฉ ุดุงุดุฉ |
|
| 25 |
+
| `terminal` | ุชูููุฐ ุฃู
ุฑ bash |
|
| 26 |
+
| `mouse_move` | ุชุญุฑูู ุงูู
ุงูุณ |
|
| 27 |
+
| `mouse_click` | ููุฑ ุงูู
ุงูุณ |
|
| 28 |
+
| `keyboard_type` | ูุชุงุจุฉ ูุต |
|
| 29 |
+
| `keyboard_hotkey` | ุงุฎุชุตุงุฑุงุช ููุญุฉ ุงูู
ูุงุชูุญ |
|
| 30 |
+
| `scroll` | ุงูุชู
ุฑูุฑ |
|
| 31 |
+
| `clipboard_read/write` | ูุฑุงุกุฉ/ูุชุงุจุฉ ุงูุญุงูุธุฉ |
|
| 32 |
+
| `start_stream` | ุจุฏุก ุงูุจุซ ุงูู
ุจุงุดุฑ |
|
| 33 |
+
| `stop_stream` | ุฅููุงู ุงูุจุซ |
|
| 34 |
+
| `open_app` | ูุชุญ ุชุทุจูู |
|
| 35 |
+
|
| 36 |
+
## ุงูุงุณุชุฎุฏุงู
|
| 37 |
+
|
| 38 |
+
ุฑุงุจุท WebSocket ุจุนุฏ ุงููุดุฑ:
|
| 39 |
+
```
|
| 40 |
+
wss://YOUR-SPACE.hf.space/ws
|
| 41 |
+
```
|
app (15) (8).py
ADDED
|
@@ -0,0 +1,1111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
THE Z AI โ Computer Mode Server v7 โ SMART CONTROL
|
| 3 |
+
====================================================
|
| 4 |
+
ุงูุฅุตูุงุญุงุช ุงูุฌููุฑูุฉ ูู v7:
|
| 5 |
+
1. capture_screen_with_grid() โ ุตูุฑุฉ ู
ุฒุฏูุฌุฉ: ูู
ูู ูุธููุฉ + ูุณุงุฑ ุจุดุจูุฉ ุงุญุฏุงุซูุงุช ุดูุงูุฉ
|
| 6 |
+
2. ุฃูุงู
ุฑ ุจุณูุทุฉ: open pc, open firefox, mouse go x y, click x y, rclick x y, type TEXT, key ENTER ...
|
| 7 |
+
3. ุฅุตูุงุญ ุงููุชุงุจุฉ ุงูุนุฑุจูุฉ: xclip clipboard + xdotool key ctrl+v
|
| 8 |
+
4. ุฅุฑุณุงู ุงุญุฏุงุซูุงุช ุงูุดุงุดุฉ ุงูุญููููุฉ + ู
ููุน ุงูู
ุงูุณ ู
ุน ูู screenshot
|
| 9 |
+
5. auto_shot_with_grid ุจุนุฏ ูู ุฎุทูุฉ
|
| 10 |
+
6. simple_command parser: ูููู
ุฃูุงู
ุฑ ุจุณูุทุฉ ููุญูููุง ูุฃูุนุงู WebSocket
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import asyncio
|
| 14 |
+
import base64
|
| 15 |
+
import io
|
| 16 |
+
import json
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
import subprocess
|
| 20 |
+
import time
|
| 21 |
+
import urllib.parse
|
| 22 |
+
|
| 23 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
| 24 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 25 |
+
from fastapi.responses import JSONResponse
|
| 26 |
+
import uvicorn
|
| 27 |
+
|
| 28 |
+
DISPLAY = os.environ.get("DISPLAY", ":1")
|
| 29 |
+
os.environ["DISPLAY"] = DISPLAY
|
| 30 |
+
|
| 31 |
+
# โโโ ุชุดุฎูุต ุฃุฏูุงุช ุงูู capture ุนูุฏ ุงูุจุฏุงูุฉ โโโโโโโโโโโโโ
|
| 32 |
+
def _check_capture_tools():
|
| 33 |
+
tools = ["scrot", "import", "xwd", "convert", "ffmpeg", "xdotool"]
|
| 34 |
+
print("โโโ Capture Tools Check โโโ")
|
| 35 |
+
for t in tools:
|
| 36 |
+
r = subprocess.run(["which", t], capture_output=True, text=True)
|
| 37 |
+
status = "โ
" if r.returncode == 0 else "โ"
|
| 38 |
+
print(f" {status} {t}: {r.stdout.strip() or 'not found'}")
|
| 39 |
+
try:
|
| 40 |
+
r2 = subprocess.run(["xdpyinfo", "-display", DISPLAY],
|
| 41 |
+
capture_output=True, text=True, timeout=3)
|
| 42 |
+
print(f" {'โ
' if r2.returncode==0 else 'โ'} DISPLAY={DISPLAY}: {'active' if r2.returncode==0 else 'not active'}")
|
| 43 |
+
except Exception as ex:
|
| 44 |
+
print(f" โ DISPLAY={DISPLAY}: {ex}")
|
| 45 |
+
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโ")
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
_check_capture_tools()
|
| 49 |
+
except Exception as _e:
|
| 50 |
+
print(f"[diag] {_e}")
|
| 51 |
+
|
| 52 |
+
# โโโ ุงูุชุดุงู ุงูู
ุชุตูุญ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 53 |
+
def _detect_browser() -> str:
|
| 54 |
+
candidates = ["firefox", "firefox-esr", "chromium-browser", "chromium", "google-chrome"]
|
| 55 |
+
found = None
|
| 56 |
+
for c in candidates:
|
| 57 |
+
r = subprocess.run(["which", c], capture_output=True, text=True)
|
| 58 |
+
if r.returncode == 0 and r.stdout.strip():
|
| 59 |
+
found = c
|
| 60 |
+
break
|
| 61 |
+
if not found:
|
| 62 |
+
return "firefox"
|
| 63 |
+
# ุชุฃูุฏ ู
ู ูุฌูุฏ firefox-esr ูู symlink
|
| 64 |
+
esr_check = subprocess.run(["which", "firefox-esr"], capture_output=True, text=True)
|
| 65 |
+
if esr_check.returncode != 0:
|
| 66 |
+
real_path = subprocess.run(["which", found], capture_output=True, text=True).stdout.strip()
|
| 67 |
+
if real_path:
|
| 68 |
+
try:
|
| 69 |
+
subprocess.run(["ln", "-sf", real_path, "/usr/local/bin/firefox-esr"], check=True)
|
| 70 |
+
print(f"โ
Created firefox-esr symlink โ {real_path}")
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f"โ ๏ธ Could not create firefox-esr symlink: {e}")
|
| 73 |
+
return found
|
| 74 |
+
|
| 75 |
+
BROWSER = _detect_browser()
|
| 76 |
+
print(f"๐ Browser detected: {BROWSER}")
|
| 77 |
+
|
| 78 |
+
app = FastAPI(title="Z-Computer-Mode API v7")
|
| 79 |
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
|
| 80 |
+
allow_methods=["*"], allow_headers=["*"])
|
| 81 |
+
|
| 82 |
+
active_connections: list[WebSocket] = []
|
| 83 |
+
stream_active = False
|
| 84 |
+
stream_fps = 3
|
| 85 |
+
stream_quality = 60
|
| 86 |
+
stream_scale = 0.5
|
| 87 |
+
|
| 88 |
+
# โโโ CURL HEADERS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 89 |
+
CURL_HEADERS = (
|
| 90 |
+
'-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
|
| 91 |
+
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" '
|
| 92 |
+
'-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" '
|
| 93 |
+
'-H "Accept-Language: en-US,en;q=0.9,ar;q=0.8" '
|
| 94 |
+
'-H "Accept-Encoding: gzip, deflate" '
|
| 95 |
+
'--compressed '
|
| 96 |
+
'--max-time 20 '
|
| 97 |
+
'-L '
|
| 98 |
+
'-s '
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
# โโโ ู
ุตุงุฏุฑ ุงูุจุญุซ 8+ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 102 |
+
def _build_search_sources(query: str) -> list[dict]:
|
| 103 |
+
q = urllib.parse.quote_plus(query)
|
| 104 |
+
q_raw = query.replace(' ', '+')
|
| 105 |
+
return [
|
| 106 |
+
{
|
| 107 |
+
"name": "DuckDuckGo Instant",
|
| 108 |
+
"cmd": f"curl -s --max-time 20 'https://api.duckduckgo.com/?q={q}&format=json&no_html=1&skip_disambig=1' | python3 -c \"import sys,json; d=json.load(sys.stdin); ans=d.get('AbstractText',''); rels=d.get('RelatedTopics',[]); print('ANSWER:',ans if ans else 'no direct answer'); [print('-',r.get('Text','')[:250]) for r in rels if isinstance(r,dict) and r.get('Text')]\""
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"name": "Google News RSS",
|
| 112 |
+
"cmd": f"curl -sL {CURL_HEADERS} 'https://news.google.com/rss/search?q={q_raw}&hl=en&gl=US&ceid=US:en' | python3 -c \"import sys,re; xml=sys.stdin.read(); titles=re.findall(r'<title><!\\[CDATA\\[(.*?)\\]\\]></title>|<title>(.*?)</title>',xml); clean=lambda s:re.sub('<[^>]+>','',s); results=[(a or b).strip() for a,b in titles if (a or b).strip()][1:9]; [print(str(i+1)+'. '+t[:180]) for i,t in enumerate(results)]\""
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"name": "Wikipedia English",
|
| 116 |
+
"cmd": f"curl -s --max-time 15 'https://en.wikipedia.org/api/rest_v1/page/summary/{q}' | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('title','')+'\\n'+d.get('extract','')[:1500])\" 2>/dev/null || curl -s --max-time 15 'https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch={q}&format=json&srlimit=5' | python3 -c \"import sys,json,re; d=json.load(sys.stdin); [print(str(i+1)+'. '+r['title']+': '+re.sub('<[^>]+>','',r.get('snippet',''))[:200]) for i,r in enumerate(d.get('query',{{}}).get('search',[]))]\" 2>/dev/null"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"name": "DuckDuckGo HTML",
|
| 120 |
+
"cmd": f"curl -sL {CURL_HEADERS} 'https://html.duckduckgo.com/html/?q={q}' | python3 -c \"import sys,re; h=sys.stdin.read(); snippets=re.findall(r'class=.result__snippet[^>]*>(.*?)</a>',h,re.DOTALL); titles=re.findall(r'class=.result__title[^>]*>.*?<a[^>]*>(.*?)</a>',h,re.DOTALL); clean=lambda s:re.sub('<[^>]+>','',s).strip(); [print(str(i+1)+'. '+clean(titles[i] if i<len(titles) else '')+'\\n '+clean(s)[:200]) for i,s in enumerate(snippets[:7])]\""
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"name": "Bing News RSS",
|
| 124 |
+
"cmd": f"curl -sL {CURL_HEADERS} 'https://www.bing.com/news/search?q={q}&format=RSS' | python3 -c \"import sys,re; xml=sys.stdin.read(); items=re.findall(r'<item>(.*?)</item>',xml,re.DOTALL); [print(str(i+1)+'. '+re.sub('<[^>]+>','',re.search(r'<title>(.*?)</title>',it).group(1) if re.search(r'<title>',it) else '')+'\\n '+re.sub('<[^>]+>','',re.search(r'<description>(.*?)</description>',it).group(1) if re.search(r'<description>',it) else '')[:150]) for i,it in enumerate(items[:6])]\""
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"name": "Reddit Search",
|
| 128 |
+
"cmd": f"curl -sL {CURL_HEADERS} -H 'Accept: application/json' 'https://www.reddit.com/search.json?q={q}&sort=new&limit=8&type=link' | python3 -c \"import sys,json; d=json.load(sys.stdin); posts=d.get('data',{{}}).get('children',[]); [print(str(i+1)+'. '+p['data'].get('title','')+'\\n r/'+p['data'].get('subreddit','')+' Score:'+str(p['data'].get('score',''))+'\\n '+p['data'].get('selftext','')[:200]) for i,p in enumerate(posts[:6])]\""
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"name": "HackerNews",
|
| 132 |
+
"cmd": f"curl -s --max-time 15 'https://hn.algolia.com/api/v1/search?query={q}&hitsPerPage=8&tags=story' | python3 -c \"import sys,json; d=json.load(sys.stdin); hits=d.get('hits',[]); [print(str(i+1)+'. '+h.get('title','')+'\\n Points:'+str(h.get('points','0'))+' | '+h.get('url','')[:80]) for i,h in enumerate(hits[:6])]\""
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"name": "ArXiv Academic",
|
| 136 |
+
"cmd": f"curl -s --max-time 15 'https://export.arxiv.org/api/query?search_query=all:{q}&start=0&max_results=5' | python3 -c \"import sys,re; xml=sys.stdin.read(); titles=re.findall(r'<title>(.*?)</title>',xml)[1:]; summaries=re.findall(r'<summary>(.*?)</summary>',xml,re.DOTALL); [print(str(i+1)+'. '+t.strip()+'\\n '+summaries[i].strip()[:200] if i<len(summaries) else '') for i,t in enumerate(titles[:5])]\""
|
| 137 |
+
},
|
| 138 |
+
]
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _extract_query_from_cmd(cmd: str) -> str:
|
| 142 |
+
patterns = [
|
| 143 |
+
r'[?&]q=([^&\'"]+)',
|
| 144 |
+
r'[?&]query=([^&\'"]+)',
|
| 145 |
+
r'[?&]search_query=all:([^&\'"]+)',
|
| 146 |
+
r"search\?q=([^&'\"\s]+)",
|
| 147 |
+
]
|
| 148 |
+
for p in patterns:
|
| 149 |
+
m = re.search(p, cmd)
|
| 150 |
+
if m:
|
| 151 |
+
q = m.group(1).replace('+', ' ').replace('%20', ' ')
|
| 152 |
+
return urllib.parse.unquote(q)
|
| 153 |
+
return ""
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _is_empty_result(stdout: str) -> bool:
|
| 157 |
+
if not stdout or len(stdout.strip()) < 10:
|
| 158 |
+
return True
|
| 159 |
+
empty_signals = [
|
| 160 |
+
"no direct answer", "no results", "0 results",
|
| 161 |
+
"no items", "[]", "{}", "error", "not found", "answer: no",
|
| 162 |
+
]
|
| 163 |
+
s = stdout.strip().lower()
|
| 164 |
+
meaningful_lines = [l for l in s.split('\n') if l.strip() and not any(sig in l for sig in empty_signals)]
|
| 165 |
+
return len(meaningful_lines) < 1
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
# โโโ Screen Capture โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 169 |
+
|
| 170 |
+
def _get_screen_size() -> tuple[int, int]:
|
| 171 |
+
"""ููุนูุฏ ุญุฌู
ุงูุดุงุดุฉ ุงูุญูููู"""
|
| 172 |
+
r = subprocess.run(["xdotool", "getdisplaygeometry"],
|
| 173 |
+
env={**os.environ, "DISPLAY": DISPLAY},
|
| 174 |
+
capture_output=True, text=True, timeout=5)
|
| 175 |
+
try:
|
| 176 |
+
parts = r.stdout.strip().split()
|
| 177 |
+
return int(parts[0]), int(parts[1])
|
| 178 |
+
except:
|
| 179 |
+
return 1920, 1080
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _get_mouse_pos() -> tuple[int, int]:
|
| 183 |
+
"""ููุนูุฏ ู
ููุน ุงูู
ุงูุณ ุงูุญุงูู"""
|
| 184 |
+
r = subprocess.run(["xdotool", "getmouselocation"],
|
| 185 |
+
env={**os.environ, "DISPLAY": DISPLAY},
|
| 186 |
+
capture_output=True, text=True, timeout=5)
|
| 187 |
+
try:
|
| 188 |
+
mx = int(re.search(r"x:(\d+)", r.stdout).group(1))
|
| 189 |
+
my = int(re.search(r"y:(\d+)", r.stdout).group(1))
|
| 190 |
+
return mx, my
|
| 191 |
+
except:
|
| 192 |
+
return 0, 0
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def capture_screen_raw() -> tuple[object, int, int]:
|
| 196 |
+
"""
|
| 197 |
+
ููุชูุท ุงูุดุงุดุฉ ุจู 5 ุทุฑู ู
ุชุณูุณูุฉ โ ุชุถู
ู ูุฌุงุญ ูุงุญุฏุฉ:
|
| 198 |
+
1. scrot (ุงูุฃุณุฑุน)
|
| 199 |
+
2. import (ImageMagick)
|
| 200 |
+
3. xwd + convert
|
| 201 |
+
4. ffmpeg + x11grab
|
| 202 |
+
5. python-xlib / Xlib ู
ุจุงุดุฑุฉ
|
| 203 |
+
"""
|
| 204 |
+
from PIL import Image
|
| 205 |
+
env = {**os.environ, "DISPLAY": DISPLAY}
|
| 206 |
+
tmp = f"/tmp/zs_{int(time.time()*1000)}"
|
| 207 |
+
|
| 208 |
+
def _load(path) -> tuple:
|
| 209 |
+
"""ุชุญู
ูู ุงูุตูุฑุฉ ุฅุฐุง ููุฌุฏุช ูุญุฌู
ูุง > 0"""
|
| 210 |
+
if not path or not os.path.exists(path):
|
| 211 |
+
return None, 0, 0
|
| 212 |
+
size = os.path.getsize(path)
|
| 213 |
+
if size < 512: # ุตูุฑุฉ ูุงุฑุบุฉ/ุชุงููุฉ
|
| 214 |
+
return None, 0, 0
|
| 215 |
+
try:
|
| 216 |
+
img = Image.open(path).convert("RGB")
|
| 217 |
+
w, h = img.size
|
| 218 |
+
if w < 10 or h < 10:
|
| 219 |
+
return None, 0, 0
|
| 220 |
+
try: os.unlink(path)
|
| 221 |
+
except: pass
|
| 222 |
+
return img, w, h
|
| 223 |
+
except Exception as ex:
|
| 224 |
+
print(f"[_load] {ex}")
|
| 225 |
+
return None, 0, 0
|
| 226 |
+
|
| 227 |
+
# โโ ุทุฑููุฉ 1: scrot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 228 |
+
try:
|
| 229 |
+
p = tmp + "_1.png"
|
| 230 |
+
r = subprocess.run(
|
| 231 |
+
["scrot", "-q", "95", p],
|
| 232 |
+
env=env, timeout=8, capture_output=True
|
| 233 |
+
)
|
| 234 |
+
img, w, h = _load(p)
|
| 235 |
+
if img:
|
| 236 |
+
print("[capture] โ
scrot")
|
| 237 |
+
return img, w, h
|
| 238 |
+
print(f"[capture] scrot failed rc={r.returncode} err={r.stderr[:80]}")
|
| 239 |
+
except Exception as e:
|
| 240 |
+
print(f"[capture] scrot ex: {e}")
|
| 241 |
+
|
| 242 |
+
# โโ ุทุฑููุฉ 2: ImageMagick import โโโโโโโโโโโโโโโโโโโโโ
|
| 243 |
+
try:
|
| 244 |
+
p = tmp + "_2.png"
|
| 245 |
+
r = subprocess.run(
|
| 246 |
+
["import", "-window", "root", "-silent", p],
|
| 247 |
+
env=env, timeout=10, capture_output=True
|
| 248 |
+
)
|
| 249 |
+
img, w, h = _load(p)
|
| 250 |
+
if img:
|
| 251 |
+
print("[capture] โ
import (ImageMagick)")
|
| 252 |
+
return img, w, h
|
| 253 |
+
print(f"[capture] import failed rc={r.returncode}")
|
| 254 |
+
except Exception as e:
|
| 255 |
+
print(f"[capture] import ex: {e}")
|
| 256 |
+
|
| 257 |
+
# โโ ุทุฑููุฉ 3: xwd + convert โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 258 |
+
try:
|
| 259 |
+
xwd_p = tmp + "_3.xwd"
|
| 260 |
+
png_p = tmp + "_3.png"
|
| 261 |
+
r1 = subprocess.run(
|
| 262 |
+
["xwd", "-root", "-silent", "-out", xwd_p],
|
| 263 |
+
env=env, timeout=10, capture_output=True
|
| 264 |
+
)
|
| 265 |
+
r2 = subprocess.run(
|
| 266 |
+
["convert", xwd_p, png_p],
|
| 267 |
+
timeout=10, capture_output=True
|
| 268 |
+
)
|
| 269 |
+
try: os.unlink(xwd_p)
|
| 270 |
+
except: pass
|
| 271 |
+
img, w, h = _load(png_p)
|
| 272 |
+
if img:
|
| 273 |
+
print("[capture] โ
xwd+convert")
|
| 274 |
+
return img, w, h
|
| 275 |
+
print(f"[capture] xwd rc={r1.returncode} convert rc={r2.returncode}")
|
| 276 |
+
except Exception as e:
|
| 277 |
+
print(f"[capture] xwd ex: {e}")
|
| 278 |
+
|
| 279 |
+
# โโ ุทุฑููุฉ 4: ffmpeg x11grab โโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 280 |
+
try:
|
| 281 |
+
p = tmp + "_4.png"
|
| 282 |
+
sw, sh = _get_screen_size()
|
| 283 |
+
r = subprocess.run([
|
| 284 |
+
"ffmpeg", "-y",
|
| 285 |
+
"-f", "x11grab",
|
| 286 |
+
"-video_size", f"{sw}x{sh}",
|
| 287 |
+
"-i", DISPLAY,
|
| 288 |
+
"-vframes", "1",
|
| 289 |
+
"-q:v", "2",
|
| 290 |
+
p
|
| 291 |
+
], env=env, timeout=12, capture_output=True)
|
| 292 |
+
img, w, h = _load(p)
|
| 293 |
+
if img:
|
| 294 |
+
print("[capture] โ
ffmpeg x11grab")
|
| 295 |
+
return img, w, h
|
| 296 |
+
print(f"[capture] ffmpeg rc={r.returncode} err={r.stderr[-120:]}")
|
| 297 |
+
except Exception as e:
|
| 298 |
+
print(f"[capture] ffmpeg ex: {e}")
|
| 299 |
+
|
| 300 |
+
# โโ ุทุฑููุฉ 5: python-xlib (Xlib ู
ุจุงุดุฑุฉ) โโโโโโโโโโโโโโ
|
| 301 |
+
try:
|
| 302 |
+
from Xlib import display as Xdisplay
|
| 303 |
+
from Xlib.ext.xtest import fake_input
|
| 304 |
+
xdisp = Xdisplay.Display(DISPLAY)
|
| 305 |
+
root = xdisp.screen().root
|
| 306 |
+
geom = root.get_geometry()
|
| 307 |
+
w, h = geom.width, geom.height
|
| 308 |
+
raw = root.get_image(0, 0, w, h,
|
| 309 |
+
Xdisplay.X.ZPixmap,
|
| 310 |
+
0xFFFFFFFF)
|
| 311 |
+
import struct
|
| 312 |
+
data = raw.data
|
| 313 |
+
# BGRA โ RGB
|
| 314 |
+
pixels = []
|
| 315 |
+
for i in range(0, len(data), 4):
|
| 316 |
+
b, g, r_, a = struct.unpack_from('BBBB', data, i)
|
| 317 |
+
pixels.extend([r_, g, b])
|
| 318 |
+
img = Image.frombytes("RGB", (w, h), bytes(pixels))
|
| 319 |
+
print("[capture] โ
python-xlib")
|
| 320 |
+
return img, w, h
|
| 321 |
+
except Exception as e:
|
| 322 |
+
print(f"[capture] xlib ex: {e}")
|
| 323 |
+
|
| 324 |
+
# โโ ุทุฑููุฉ 6: ุตูุฑุฉ placeholder ูุงุถ๏ฟฝ๏ฟฝุฉ โโโโโโโโโโโโโโโโ
|
| 325 |
+
print("[capture] โ ๏ธ ALL methods failed โ returning placeholder")
|
| 326 |
+
try:
|
| 327 |
+
from PIL import ImageDraw
|
| 328 |
+
sw, sh = _get_screen_size()
|
| 329 |
+
img = Image.new("RGB", (sw or 1280, sh or 720), (20, 20, 30))
|
| 330 |
+
draw = ImageDraw.Draw(img)
|
| 331 |
+
draw.rectangle([(0, 0), (sw, 40)], fill=(30, 30, 50))
|
| 332 |
+
draw.text((10, 10), f"โ ๏ธ Screenshot failed โ DISPLAY={DISPLAY}", fill=(255, 100, 100))
|
| 333 |
+
draw.text((10, 50), "Methods tried: scrot, import, xwd, ffmpeg, xlib", fill=(150, 150, 150))
|
| 334 |
+
return img, sw or 1280, sh or 720
|
| 335 |
+
except:
|
| 336 |
+
return None, 0, 0
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def capture_screen(scale=0.6, quality=70) -> str:
|
| 340 |
+
"""ููุชูุท ุงูุดุงุดุฉ ุงูุนุงุฏูุฉ ุจุฏูู grid"""
|
| 341 |
+
img, w, h = capture_screen_raw()
|
| 342 |
+
if img is None:
|
| 343 |
+
return ""
|
| 344 |
+
try:
|
| 345 |
+
if scale < 1.0:
|
| 346 |
+
img = img.resize((int(w * scale), int(h * scale)), img.LANCZOS)
|
| 347 |
+
buf = io.BytesIO()
|
| 348 |
+
img.save(buf, format="JPEG", quality=quality, optimize=True)
|
| 349 |
+
return base64.b64encode(buf.getvalue()).decode()
|
| 350 |
+
except Exception as e:
|
| 351 |
+
print(f"[capture] {e}")
|
| 352 |
+
return ""
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def capture_screen_with_grid(scale=0.65, quality=72,
|
| 356 |
+
force_mx: int | None = None,
|
| 357 |
+
force_my: int | None = None) -> dict:
|
| 358 |
+
"""
|
| 359 |
+
ููุชูุท ุงูุดุงุดุฉ ูููุนูุฏ ุตูุฑุฉ ูุงุญุฏุฉ ููุท ู
ุน ุดุจูุฉ ุฅุญุฏุงุซูุงุช ูุงุถุญุฉ.
|
| 360 |
+
ุงูุฅุญุฏุงุซูุงุช ูู ุงูุตูุฑุฉ ุชุทุงุจู ุฅุญุฏุงุซูุงุช ุงูุดุงุดุฉ ุงูุญููููุฉ (1:1 mapping).
|
| 361 |
+
ุงูุฎุทูุท ูู 100px โ ุงูุฃุฑูุงู
ุชูุธูุฑ ุงูู X/Y ุงูุญูููู ููููุฑ.
|
| 362 |
+
|
| 363 |
+
force_mx / force_my: ุฅุฐุง ู
ูุฑููุฑุง ููุฑุณู
ุงูู cursor ูู ูุฐุง ุงูู
ูุถุน ู
ุจุงุดุฑุฉู
|
| 364 |
+
ุจุฏูู ุงุณุชุนูุงู
xdotool โ ููุนุงูุฌ ู
ุดููุฉ race condition ุจุนุฏ mouse_move.
|
| 365 |
+
"""
|
| 366 |
+
try:
|
| 367 |
+
from PIL import Image, ImageDraw
|
| 368 |
+
img, orig_w, orig_h = capture_screen_raw()
|
| 369 |
+
if img is None:
|
| 370 |
+
return {"data": "", "width": 1920, "height": 1080, "mouse_x": 0, "mouse_y": 0}
|
| 371 |
+
|
| 372 |
+
# ุงุณุชุฎุฏู
ุงูุฅุญุฏุงุซูุงุช ุงูู
ูู
ุฑููุฑุฉ ุฅุฐุง ููุฌุฏุช (ุจุนุฏ mouse_move/click ู
ุจุงุดุฑุฉ)
|
| 373 |
+
# ูุฅูุง ุงูุฑุฃ ู
ู X server
|
| 374 |
+
if force_mx is not None and force_my is not None:
|
| 375 |
+
mx, my = force_mx, force_my
|
| 376 |
+
else:
|
| 377 |
+
mx, my = _get_mouse_pos()
|
| 378 |
+
|
| 379 |
+
# resize ููุนุฑุถ ููุท โ ุงูุฅุญุฏุงุซูุงุช ุชุจูู ููุดุงุดุฉ ุงูุฃุตููุฉ
|
| 380 |
+
sw = int(orig_w * scale)
|
| 381 |
+
sh = int(orig_h * scale)
|
| 382 |
+
grid_img = img.resize((sw, sh), Image.LANCZOS)
|
| 383 |
+
draw = ImageDraw.Draw(grid_img, "RGBA")
|
| 384 |
+
|
| 385 |
+
# ุดุจูุฉ ูู 100px ุจุฅุญุฏุงุซูุงุช ุงูุดุงุดุฉ ุงูุญููููุฉ
|
| 386 |
+
step_orig = 100
|
| 387 |
+
step_scaled_x = int(step_orig * sw / orig_w)
|
| 388 |
+
step_scaled_y = int(step_orig * sh / orig_h)
|
| 389 |
+
|
| 390 |
+
grid_color = (255, 255, 255, 45) # ุฎุทูุท ุจูุถุงุก ุดูุงูุฉ
|
| 391 |
+
label_color = (0, 255, 180, 200) # ุฃุฑูุงู
ุฎุถุฑุงุก ูุงุถุญุฉ
|
| 392 |
+
|
| 393 |
+
# ุฎุทูุท ุนู
ูุฏูุฉ + ุฃุฑูุงู
X ุงูุญููููุฉ
|
| 394 |
+
x_sc = step_scaled_x
|
| 395 |
+
x_real = step_orig
|
| 396 |
+
while x_sc < sw:
|
| 397 |
+
draw.line([(x_sc, 0), (x_sc, sh)], fill=grid_color, width=1)
|
| 398 |
+
draw.rectangle([(x_sc + 1, 2), (x_sc + 32, 14)], fill=(0, 0, 0, 160))
|
| 399 |
+
draw.text((x_sc + 2, 3), str(x_real), fill=label_color)
|
| 400 |
+
x_sc += step_scaled_x
|
| 401 |
+
x_real += step_orig
|
| 402 |
+
|
| 403 |
+
# ุฎุทูุท ุฃูููุฉ + ุฃุฑูุงู
Y ุงูุญููููุฉ
|
| 404 |
+
y_sc = step_scaled_y
|
| 405 |
+
y_real = step_orig
|
| 406 |
+
while y_sc < sh:
|
| 407 |
+
draw.line([(0, y_sc), (sw, y_sc)], fill=grid_color, width=1)
|
| 408 |
+
draw.rectangle([(2, y_sc + 1), (36, y_sc + 13)], fill=(0, 0, 0, 160))
|
| 409 |
+
draw.text((3, y_sc + 2), str(y_real), fill=label_color)
|
| 410 |
+
y_sc += step_scaled_y
|
| 411 |
+
y_real += step_orig
|
| 412 |
+
|
| 413 |
+
# ู
ููุน ุงูู
ุงูุณ ุงูุญูููู โ ุฏุงุฆุฑุฉ ุญู
ุฑุงุก
|
| 414 |
+
mouse_sx = int(mx * sw / orig_w)
|
| 415 |
+
mouse_sy = int(my * sh / orig_h)
|
| 416 |
+
r = 10
|
| 417 |
+
draw.ellipse([(mouse_sx-r, mouse_sy-r), (mouse_sx+r, mouse_sy+r)],
|
| 418 |
+
outline=(255, 60, 60, 240), width=2)
|
| 419 |
+
draw.line([(mouse_sx-16, mouse_sy), (mouse_sx+16, mouse_sy)],
|
| 420 |
+
fill=(255, 60, 60, 200), width=1)
|
| 421 |
+
draw.line([(mouse_sx, mouse_sy-16), (mouse_sx, mouse_sy+16)],
|
| 422 |
+
fill=(255, 60, 60, 200), width=1)
|
| 423 |
+
|
| 424 |
+
# ุดุฑูุท ู
ุนููู
ุงุช ูู ุงูุฃุนูู
|
| 425 |
+
final = grid_img.convert("RGB")
|
| 426 |
+
draw2 = ImageDraw.Draw(final)
|
| 427 |
+
draw2.rectangle([(0, 0), (sw, 18)], fill=(0, 0, 0))
|
| 428 |
+
hdr = (f"SCREEN {orig_w}x{orig_h} | MOUSE:({mx},{my}) | "
|
| 429 |
+
f"GRID=100px | CLICK COORDS = numbers on grid lines")
|
| 430 |
+
draw2.text((4, 2), hdr, fill=(0, 220, 160))
|
| 431 |
+
|
| 432 |
+
# ุดุฑูุท ู
ุนููู
ุงุช ูู ุงูุฃุณูู
|
| 433 |
+
draw2.rectangle([(0, sh-18), (sw, sh)], fill=(0, 0, 0))
|
| 434 |
+
draw2.text((4, sh-16),
|
| 435 |
+
f"USE REAL COORDS: e.g. click x=500 y=300 means the '500' vertical line + '300' horizontal line",
|
| 436 |
+
fill=(180, 180, 80))
|
| 437 |
+
|
| 438 |
+
buf = io.BytesIO()
|
| 439 |
+
final.save(buf, format="JPEG", quality=quality, optimize=True)
|
| 440 |
+
data = base64.b64encode(buf.getvalue()).decode()
|
| 441 |
+
|
| 442 |
+
return {
|
| 443 |
+
"data": data,
|
| 444 |
+
"width": orig_w,
|
| 445 |
+
"height": orig_h,
|
| 446 |
+
"mouse_x": mx,
|
| 447 |
+
"mouse_y": my,
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
except Exception as e:
|
| 451 |
+
print(f"[capture_grid] {e}")
|
| 452 |
+
plain = capture_screen(scale=scale, quality=quality)
|
| 453 |
+
mx, my = _get_mouse_pos()
|
| 454 |
+
w, h = _get_screen_size()
|
| 455 |
+
return {"data": plain, "width": w, "height": h, "mouse_x": mx, "mouse_y": my}
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
# โโโ Command Runner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 459 |
+
|
| 460 |
+
def run_raw_command(cmd: str, timeout: int = 60) -> dict:
|
| 461 |
+
env = {**os.environ, "DISPLAY": DISPLAY,
|
| 462 |
+
"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
|
| 463 |
+
try:
|
| 464 |
+
result = subprocess.run(cmd, shell=True, capture_output=True,
|
| 465 |
+
text=True, timeout=timeout, env=env,
|
| 466 |
+
executable="/bin/bash")
|
| 467 |
+
return {
|
| 468 |
+
"stdout": result.stdout[-15000:],
|
| 469 |
+
"stderr": result.stderr[-3000:],
|
| 470 |
+
"returncode": result.returncode,
|
| 471 |
+
}
|
| 472 |
+
except subprocess.TimeoutExpired:
|
| 473 |
+
return {"stdout": "", "stderr": f"โฑ๏ธ Timeout {timeout}s", "returncode": -1}
|
| 474 |
+
except Exception as e:
|
| 475 |
+
return {"stdout": "", "stderr": str(e), "returncode": -1}
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def run_command_smart(cmd: str, timeout: int = 60) -> dict:
|
| 479 |
+
res = run_raw_command(cmd, timeout=timeout)
|
| 480 |
+
stdout = res["stdout"].strip()
|
| 481 |
+
|
| 482 |
+
if res["returncode"] == 0 and not _is_empty_result(stdout):
|
| 483 |
+
return res
|
| 484 |
+
|
| 485 |
+
is_curl_search = "curl" in cmd and any(x in cmd for x in [
|
| 486 |
+
"duckduckgo", "google", "bing", "wikipedia", "reddit",
|
| 487 |
+
"hackernews", "hn.algolia", "arxiv", "news", "search"
|
| 488 |
+
])
|
| 489 |
+
|
| 490 |
+
if not is_curl_search:
|
| 491 |
+
return res
|
| 492 |
+
|
| 493 |
+
query = _extract_query_from_cmd(cmd)
|
| 494 |
+
if not query or len(query) < 3:
|
| 495 |
+
words = [w for w in cmd.split() if len(w) > 3 and not w.startswith('-')
|
| 496 |
+
and 'http' not in w and 'python3' not in w and 'curl' not in w]
|
| 497 |
+
query = ' '.join(words[:5]) if words else ""
|
| 498 |
+
|
| 499 |
+
if not query:
|
| 500 |
+
return res
|
| 501 |
+
|
| 502 |
+
sources = _build_search_sources(query)
|
| 503 |
+
tried_names = []
|
| 504 |
+
all_results = []
|
| 505 |
+
|
| 506 |
+
for source in sources:
|
| 507 |
+
src_name = source["name"]
|
| 508 |
+
tried_names.append(f"๐ {src_name}")
|
| 509 |
+
src_res = run_raw_command(source["cmd"], timeout=25)
|
| 510 |
+
src_out = src_res["stdout"].strip()
|
| 511 |
+
|
| 512 |
+
if not _is_empty_result(src_out):
|
| 513 |
+
combined_header = f"[ู
ุตุฏุฑ ุจุฏูู: {src_name}]\n{'='*50}\n"
|
| 514 |
+
all_results.append(src_out)
|
| 515 |
+
for source2 in sources:
|
| 516 |
+
if source2["name"] != src_name:
|
| 517 |
+
s2 = run_raw_command(source2["cmd"], timeout=20)
|
| 518 |
+
s2_out = s2["stdout"].strip()
|
| 519 |
+
if not _is_empty_result(s2_out):
|
| 520 |
+
all_results.append(f"\n[ู
ุตุฏุฑ ุฅุถุงูู: {source2['name']}]\n{s2_out}")
|
| 521 |
+
break
|
| 522 |
+
final_out = combined_header + "\n\n".join(all_results)
|
| 523 |
+
return {
|
| 524 |
+
"stdout": final_out[:15000],
|
| 525 |
+
"stderr": "",
|
| 526 |
+
"returncode": 0,
|
| 527 |
+
"_sources_tried": tried_names,
|
| 528 |
+
"_fallback_used": src_name,
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
return {
|
| 532 |
+
"stdout": f"[ูู
ุชูุฑุฌุน ุฃู ู
ุตุงุฏุฑ ูุชุงุฆุฌ ูู: {query}]\nุงูู
ุตุงุฏุฑ: {', '.join(tried_names[:5])}",
|
| 533 |
+
"stderr": res["stderr"],
|
| 534 |
+
"returncode": -1,
|
| 535 |
+
"_sources_tried": tried_names,
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
def xdo(args: list, timeout=10) -> dict:
|
| 540 |
+
r = subprocess.run(["xdotool"] + args,
|
| 541 |
+
env={**os.environ, "DISPLAY": DISPLAY},
|
| 542 |
+
timeout=timeout, capture_output=True, text=True)
|
| 543 |
+
return {"rc": r.returncode, "out": r.stdout, "err": r.stderr}
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def type_text_smart(text: str) -> dict:
|
| 547 |
+
"""
|
| 548 |
+
ูุชุงุจุฉ ูุต ุฐููุฉ โ ุชุฏุนู
ุงูุนุฑุจูุฉ ูุงูุฅูุฌููุฒูุฉ:
|
| 549 |
+
- ูููุตูุต ุงูุฅูุฌููุฒูุฉ: xdotool type ู
ุจุงุดุฑุฉ
|
| 550 |
+
- ูููุตูุต ุงูุนุฑุจูุฉ ุฃู ุงูู
ุฎุชูุทุฉ: xclip clipboard ุซู
ctrl+v
|
| 551 |
+
"""
|
| 552 |
+
has_arabic = bool(re.search(r'[\u0600-\u06FF]', text))
|
| 553 |
+
|
| 554 |
+
if has_arabic:
|
| 555 |
+
# ุทุฑููุฉ Clipboard ูุถู
ุงู ูุชุงุจุฉ ุงูุนุฑุจูุฉ ุจุดูู ุตุญูุญ
|
| 556 |
+
try:
|
| 557 |
+
proc = subprocess.Popen(
|
| 558 |
+
["xclip", "-selection", "clipboard"],
|
| 559 |
+
stdin=subprocess.PIPE,
|
| 560 |
+
env={**os.environ, "DISPLAY": DISPLAY}
|
| 561 |
+
)
|
| 562 |
+
proc.communicate(text.encode("utf-8"))
|
| 563 |
+
time.sleep(0.15)
|
| 564 |
+
# Focus + paste
|
| 565 |
+
xdo(["key", "--clearmodifiers", "ctrl+v"])
|
| 566 |
+
return {"success": True, "method": "clipboard+paste", "arabic": True}
|
| 567 |
+
except Exception as e:
|
| 568 |
+
# fallback: xdotool type
|
| 569 |
+
r = xdo(["type", "--clearmodifiers", "--delay", "50", text])
|
| 570 |
+
return {"success": r["rc"] == 0, "method": "xdotool_fallback", "error": r["err"]}
|
| 571 |
+
else:
|
| 572 |
+
# ุฅูุฌููุฒูุฉ: xdotool type ู
ุจุงุดุฑุฉ
|
| 573 |
+
r = xdo(["type", "--clearmodifiers", "--delay", "30", text])
|
| 574 |
+
return {"success": r["rc"] == 0, "method": "xdotool_direct"}
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
def open_browser_smart(url: str = "") -> str:
|
| 578 |
+
"""ููุชุญ ุงูู
ุชุตูุญ ุจุทุฑููุฉ ุฐููุฉ ู
ุน fallback"""
|
| 579 |
+
browser_cmd = BROWSER
|
| 580 |
+
if not url:
|
| 581 |
+
url = "about:blank"
|
| 582 |
+
|
| 583 |
+
# ุฌุฑุจ ุฃููุงู: BROWSER ุงูุนุงุฏู
|
| 584 |
+
cmd = f"{browser_cmd} --new-window '{url}' &"
|
| 585 |
+
proc = subprocess.Popen(cmd, shell=True,
|
| 586 |
+
env={**os.environ, "DISPLAY": DISPLAY},
|
| 587 |
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 588 |
+
return f"{browser_cmd} โ {url}"
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
# โโโ Simple Command Parser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 592 |
+
# ูููู
ุฃูุงู
ุฑ ุจุณูุทุฉ ููุญูููุง ูุฃูุนุงู ุฏุงุฎููุฉ
|
| 593 |
+
|
| 594 |
+
def parse_simple_command(raw: str) -> dict | None:
|
| 595 |
+
"""
|
| 596 |
+
ููุญููู ุงูุฃูุงู
ุฑ ุงูุจุณูุทุฉ ุงููุตูุฉ ูุชูุนูุฏ action dict
|
| 597 |
+
ุฃู
ุซูุฉ:
|
| 598 |
+
open pc โ {"action":"screenshot"}
|
| 599 |
+
open firefox โ {"action":"open_browser","url":""}
|
| 600 |
+
open chrome โ {"action":"open_browser","url":""}
|
| 601 |
+
open browser โ {"action":"open_browser","url":""}
|
| 602 |
+
open url https://google.com โ {"action":"open_browser","url":"https://..."}
|
| 603 |
+
mouse go x500 y300 โ {"action":"mouse_move","x":500,"y":300}
|
| 604 |
+
mouse go 500 300 โ {"action":"mouse_move","x":500,"y":300}
|
| 605 |
+
click x500 y300 โ {"action":"mouse_click","x":500,"y":300,"button":"left"}
|
| 606 |
+
click 500 300 โ {"action":"mouse_click","x":500,"y":300,"button":"left"}
|
| 607 |
+
rclick x500 y300 โ {"action":"mouse_click","x":500,"y":300,"button":"right"}
|
| 608 |
+
dclick x500 y300 โ {"action":"mouse_click","x":500,"y":300,"double":true}
|
| 609 |
+
type hello world โ {"action":"keyboard_type","text":"hello world"}
|
| 610 |
+
key enter โ {"action":"keyboard_hotkey","keys":["Return"]}
|
| 611 |
+
key ctrl+c โ {"action":"keyboard_hotkey","keys":["ctrl","c"]}
|
| 612 |
+
scroll up โ {"action":"scroll","x":960,"y":540,"clicks":3}
|
| 613 |
+
scroll down โ {"action":"scroll","x":960,"y":540,"clicks":-3}
|
| 614 |
+
screenshot โ {"action":"screenshot"}
|
| 615 |
+
screen info โ {"action":"screen_info"}
|
| 616 |
+
"""
|
| 617 |
+
s = raw.strip().lower()
|
| 618 |
+
|
| 619 |
+
# open pc / open computer / open screen
|
| 620 |
+
if re.match(r'^open\s+(pc|computer|screen|desktop)$', s):
|
| 621 |
+
return {"action": "screenshot"}
|
| 622 |
+
|
| 623 |
+
# open firefox / chrome / browser / browser url
|
| 624 |
+
m = re.match(r'^open\s+(firefox|chrome|chromium|browser|web|internet)(\s+(.+))?$', s)
|
| 625 |
+
if m:
|
| 626 |
+
url = (m.group(3) or "").strip()
|
| 627 |
+
if url and not url.startswith("http"):
|
| 628 |
+
url = "https://" + url
|
| 629 |
+
return {"action": "open_browser", "url": url or ""}
|
| 630 |
+
|
| 631 |
+
# open url <url>
|
| 632 |
+
m = re.match(r'^open\s+url\s+(\S+)$', s)
|
| 633 |
+
if m:
|
| 634 |
+
url = m.group(1)
|
| 635 |
+
if not url.startswith("http"):
|
| 636 |
+
url = "https://" + url
|
| 637 |
+
return {"action": "open_browser", "url": url}
|
| 638 |
+
|
| 639 |
+
# mouse go x500 y300 OR mouse go 500 300
|
| 640 |
+
m = re.match(r'^mouse\s+(?:go|move|to)\s+x?(\d+)\s+y?(\d+)$', s)
|
| 641 |
+
if m:
|
| 642 |
+
return {"action": "mouse_move", "x": int(m.group(1)), "y": int(m.group(2))}
|
| 643 |
+
|
| 644 |
+
# click x500 y300 OR click 500 300
|
| 645 |
+
m = re.match(r'^click\s+x?(\d+)\s+y?(\d+)$', s)
|
| 646 |
+
if m:
|
| 647 |
+
return {"action": "mouse_click", "x": int(m.group(1)), "y": int(m.group(2)), "button": "left"}
|
| 648 |
+
|
| 649 |
+
# rclick / right click
|
| 650 |
+
m = re.match(r'^r(?:ight)?click\s+x?(\d+)\s+y?(\d+)$', s)
|
| 651 |
+
if m:
|
| 652 |
+
return {"action": "mouse_click", "x": int(m.group(1)), "y": int(m.group(2)), "button": "right"}
|
| 653 |
+
|
| 654 |
+
# dclick / double click
|
| 655 |
+
m = re.match(r'^d(?:ouble)?click\s+x?(\d+)\s+y?(\d+)$', s)
|
| 656 |
+
if m:
|
| 657 |
+
return {"action": "mouse_click", "x": int(m.group(1)), "y": int(m.group(2)), "button": "left", "double": True}
|
| 658 |
+
|
| 659 |
+
# type <text> (ูุญุชูุธ ุจุงูุญุงูุฉ ุงูุฃุตููุฉ)
|
| 660 |
+
m = re.match(r'^type\s+(.+)$', raw.strip(), re.IGNORECASE)
|
| 661 |
+
if m:
|
| 662 |
+
return {"action": "keyboard_type", "text": m.group(1)}
|
| 663 |
+
|
| 664 |
+
# key <keys>
|
| 665 |
+
m = re.match(r'^key\s+(.+)$', s)
|
| 666 |
+
if m:
|
| 667 |
+
k = m.group(1).strip()
|
| 668 |
+
key_map = {
|
| 669 |
+
"enter": "Return", "return": "Return", "esc": "Escape", "escape": "Escape",
|
| 670 |
+
"tab": "Tab", "space": "space", "backspace": "BackSpace", "delete": "Delete",
|
| 671 |
+
"up": "Up", "down": "Down", "left": "Left", "right": "Right",
|
| 672 |
+
"home": "Home", "end": "End", "pageup": "Prior", "pagedown": "Next",
|
| 673 |
+
"f1": "F1", "f2": "F2", "f3": "F3", "f4": "F4", "f5": "F5",
|
| 674 |
+
}
|
| 675 |
+
if '+' in k:
|
| 676 |
+
parts = [p.strip() for p in k.split('+')]
|
| 677 |
+
keys = [key_map.get(p, p) for p in parts]
|
| 678 |
+
else:
|
| 679 |
+
keys = [key_map.get(k, k)]
|
| 680 |
+
return {"action": "keyboard_hotkey", "keys": keys}
|
| 681 |
+
|
| 682 |
+
# scroll up / scroll down / scroll <n>
|
| 683 |
+
m = re.match(r'^scroll\s+(up|down|(\-?\d+))$', s)
|
| 684 |
+
if m:
|
| 685 |
+
w, h = _get_screen_size()
|
| 686 |
+
direction = m.group(1)
|
| 687 |
+
if direction == "up":
|
| 688 |
+
clicks = 4
|
| 689 |
+
elif direction == "down":
|
| 690 |
+
clicks = -4
|
| 691 |
+
else:
|
| 692 |
+
clicks = int(direction)
|
| 693 |
+
return {"action": "scroll", "x": w // 2, "y": h // 2, "clicks": clicks}
|
| 694 |
+
|
| 695 |
+
# screenshot
|
| 696 |
+
if s in ("screenshot", "screen", "ss", "snap"):
|
| 697 |
+
return {"action": "screenshot"}
|
| 698 |
+
|
| 699 |
+
# screen info
|
| 700 |
+
if re.match(r'^screen\s+info$', s):
|
| 701 |
+
return {"action": "screen_info"}
|
| 702 |
+
|
| 703 |
+
return None # ูู
ููุนุฑู
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
async def broadcast(msg: dict):
|
| 707 |
+
txt = json.dumps(msg, ensure_ascii=False)
|
| 708 |
+
dead = []
|
| 709 |
+
for ws in active_connections:
|
| 710 |
+
try: await ws.send_text(txt)
|
| 711 |
+
except: dead.append(ws)
|
| 712 |
+
for ws in dead:
|
| 713 |
+
if ws in active_connections:
|
| 714 |
+
active_connections.remove(ws)
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
async def screen_stream_loop():
|
| 718 |
+
global stream_active
|
| 719 |
+
interval = 1.0 / max(1, stream_fps)
|
| 720 |
+
while stream_active and active_connections:
|
| 721 |
+
try:
|
| 722 |
+
frame = capture_screen(scale=stream_scale, quality=stream_quality)
|
| 723 |
+
if frame:
|
| 724 |
+
await broadcast({"type": "frame", "data": frame})
|
| 725 |
+
except: pass
|
| 726 |
+
await asyncio.sleep(interval)
|
| 727 |
+
stream_active = False
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
# โโโ Action Handler โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 731 |
+
|
| 732 |
+
async def handle_action(ws: WebSocket, msg: dict):
|
| 733 |
+
action = msg.get("action", "")
|
| 734 |
+
data = msg.get("data", {})
|
| 735 |
+
|
| 736 |
+
async def send(obj):
|
| 737 |
+
await ws.send_text(json.dumps(obj, ensure_ascii=False))
|
| 738 |
+
|
| 739 |
+
async def auto_shot_grid(label="", delay=0.5,
|
| 740 |
+
force_mx: int | None = None,
|
| 741 |
+
force_my: int | None = None):
|
| 742 |
+
"""
|
| 743 |
+
screenshot ู
ุน grid.
|
| 744 |
+
force_mx/force_my: ุงุฑุณู
ุงูู cursor ูู ูุฐุง ุงูู
ูุถุน ุจุงูุถุจุท (ุจุนุฏ mouse_move/click)
|
| 745 |
+
ุจุฏูุงู ู
ู ุฅุนุงุฏุฉ ูุฑุงุกุชู ู
ู X server โ ููุตูุญ race condition.
|
| 746 |
+
"""
|
| 747 |
+
await asyncio.sleep(delay)
|
| 748 |
+
result = capture_screen_with_grid(scale=0.65, quality=72,
|
| 749 |
+
force_mx=force_mx, force_my=force_my)
|
| 750 |
+
if result["data"]:
|
| 751 |
+
await send({
|
| 752 |
+
"type": "screenshot",
|
| 753 |
+
"data": result["data"],
|
| 754 |
+
"ts": int(time.time() * 1000),
|
| 755 |
+
"auto": True,
|
| 756 |
+
"label": label,
|
| 757 |
+
"screen_width": result["width"],
|
| 758 |
+
"screen_height": result["height"],
|
| 759 |
+
"mouse_x": result["mouse_x"],
|
| 760 |
+
"mouse_y": result["mouse_y"],
|
| 761 |
+
"has_grid": True,
|
| 762 |
+
})
|
| 763 |
+
return result["data"]
|
| 764 |
+
|
| 765 |
+
# โโ screenshot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 766 |
+
if action == "screenshot":
|
| 767 |
+
result = capture_screen_with_grid(scale=0.65, quality=75)
|
| 768 |
+
await send({
|
| 769 |
+
"type": "screenshot",
|
| 770 |
+
"data": result["data"],
|
| 771 |
+
"ts": int(time.time() * 1000),
|
| 772 |
+
"screen_width": result["width"],
|
| 773 |
+
"screen_height": result["height"],
|
| 774 |
+
"mouse_x": result["mouse_x"],
|
| 775 |
+
"mouse_y": result["mouse_y"],
|
| 776 |
+
"has_grid": True,
|
| 777 |
+
})
|
| 778 |
+
|
| 779 |
+
# โโ simple_command โ ุงูุฃูุงู
ุฑ ุงูุจุณูุทุฉ โโโโโโโโโโโโโโ
|
| 780 |
+
elif action == "simple_command":
|
| 781 |
+
raw_cmd = data.get("cmd", "").strip()
|
| 782 |
+
parsed = parse_simple_command(raw_cmd)
|
| 783 |
+
if parsed is None:
|
| 784 |
+
await send({
|
| 785 |
+
"type": "simple_command_result",
|
| 786 |
+
"ok": False,
|
| 787 |
+
"cmd": raw_cmd,
|
| 788 |
+
"error": f"ุฃู
ุฑ ุบูุฑ ู
ุนุฑูู: '{raw_cmd}'\nุงูุฃูุงู
ุฑ ุงูู
ุชุงุญุฉ: open pc, open firefox, mouse go x y, click x y, rclick x y, type TEXT, key ENTER, scroll up/down, screenshot"
|
| 789 |
+
})
|
| 790 |
+
return
|
| 791 |
+
# ูููุฐ ุงูุฃู
ุฑ ุงูู
ูุญููู
|
| 792 |
+
await send({"type": "simple_command_result", "ok": True, "cmd": raw_cmd, "parsed": parsed})
|
| 793 |
+
# ุฃุนูุฏ ุชุดุบูู ููุณ ุงูู handle_action ู
ุน ุงูุฃู
ุฑ ุงูู
ูุญููู
|
| 794 |
+
sub_msg = {"action": parsed["action"], "data": {k: v for k, v in parsed.items() if k != "action"}}
|
| 795 |
+
await handle_action(ws, sub_msg)
|
| 796 |
+
return
|
| 797 |
+
|
| 798 |
+
# โโ terminal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 799 |
+
elif action == "terminal":
|
| 800 |
+
cmd = data.get("cmd", "")
|
| 801 |
+
timeout = int(data.get("timeout", 60))
|
| 802 |
+
if not cmd:
|
| 803 |
+
await send({"type": "terminal_result", "cmd": "", "stdout": "",
|
| 804 |
+
"stderr": "no command", "returncode": -1})
|
| 805 |
+
return
|
| 806 |
+
res = run_command_smart(cmd, timeout=timeout)
|
| 807 |
+
await send({
|
| 808 |
+
"type": "terminal_result",
|
| 809 |
+
"cmd": cmd,
|
| 810 |
+
"stdout": res["stdout"],
|
| 811 |
+
"stderr": res.get("stderr", ""),
|
| 812 |
+
"returncode": res["returncode"],
|
| 813 |
+
"fallback_used": res.get("_fallback_used", None),
|
| 814 |
+
"sources_tried": res.get("_sources_tried", []),
|
| 815 |
+
})
|
| 816 |
+
await auto_shot_grid(f"ุจุนุฏ: {cmd[:45]}", delay=0.4)
|
| 817 |
+
|
| 818 |
+
# โโ mouse_move โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 819 |
+
elif action == "mouse_move":
|
| 820 |
+
x, y = int(data.get("x", 0)), int(data.get("y", 0))
|
| 821 |
+
# --sync ููุชุธุฑ ุญุชู ูุคูุฏ X server ุงุณุชูุงู
ุงูุฃู
ุฑ
|
| 822 |
+
xdo(["mousemove", "--sync", str(x), str(y)])
|
| 823 |
+
await send({"type": "ack", "action": "mouse_move", "x": x, "y": y})
|
| 824 |
+
# ู
ุฑูุฑ ุงูุฅุญุฏุงุซูุงุช ู
ุจุงุดุฑุฉู ูุชุฌููุจ race condition ู
ุน X server
|
| 825 |
+
await auto_shot_grid(f"ู
ุงูุณ โ ({x},{y})", delay=0.2,
|
| 826 |
+
force_mx=x, force_my=y)
|
| 827 |
+
|
| 828 |
+
# โโ mouse_click โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 829 |
+
elif action == "mouse_click":
|
| 830 |
+
x, y = int(data.get("x", 0)), int(data.get("y", 0))
|
| 831 |
+
btn_num = {"left": "1", "middle": "2", "right": "3"}.get(data.get("button", "left"), "1")
|
| 832 |
+
xdo(["mousemove", "--sync", str(x), str(y)])
|
| 833 |
+
await asyncio.sleep(0.08)
|
| 834 |
+
if data.get("double"):
|
| 835 |
+
xdo(["click", "--repeat", "2", "--delay", "100", btn_num])
|
| 836 |
+
else:
|
| 837 |
+
xdo(["click", btn_num])
|
| 838 |
+
btn_name = {"1": "left", "2": "middle", "3": "right"}.get(btn_num, "left")
|
| 839 |
+
await send({"type": "ack", "action": "mouse_click", "x": x, "y": y, "button": btn_name})
|
| 840 |
+
# ู
ุฑูุฑ ุงูุฅุญุฏุงุซูุงุช ู
ุจุงุดุฑุฉู ูุชุฌููุจ race condition
|
| 841 |
+
await auto_shot_grid(f"ููุฑ {btn_name} โ ({x},{y})", delay=0.5,
|
| 842 |
+
force_mx=x, force_my=y)
|
| 843 |
+
|
| 844 |
+
# โโ keyboard_type โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 845 |
+
elif action == "keyboard_type":
|
| 846 |
+
text = data.get("text", "")
|
| 847 |
+
if text:
|
| 848 |
+
result = type_text_smart(text)
|
| 849 |
+
await send({"type": "ack", "action": "keyboard_type",
|
| 850 |
+
"method": result["method"], "text_len": len(text)})
|
| 851 |
+
else:
|
| 852 |
+
await send({"type": "ack", "action": "keyboard_type"})
|
| 853 |
+
await auto_shot_grid("ุจุนุฏ ุงููุชุงุจุฉ", delay=0.5)
|
| 854 |
+
|
| 855 |
+
# โโ keyboard_hotkey โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 856 |
+
elif action == "keyboard_hotkey":
|
| 857 |
+
keys = data.get("keys", [])
|
| 858 |
+
if keys:
|
| 859 |
+
xdo(["key", "--clearmodifiers", "+".join(keys)])
|
| 860 |
+
await send({"type": "ack", "action": "keyboard_hotkey", "keys": keys})
|
| 861 |
+
await auto_shot_grid("ุจุนุฏ ุงูุงุฎุชุตุงุฑ", delay=0.5)
|
| 862 |
+
|
| 863 |
+
# โโ keyboard_press โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 864 |
+
elif action == "keyboard_press":
|
| 865 |
+
key = data.get("key", "")
|
| 866 |
+
if key:
|
| 867 |
+
xdo(["key", "--clearmodifiers", key])
|
| 868 |
+
await send({"type": "ack", "action": "keyboard_press"})
|
| 869 |
+
await auto_shot_grid("ุจุนุฏ ุงูู
ูุชุงุญ", delay=0.4)
|
| 870 |
+
|
| 871 |
+
# โโ clipboard_write โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 872 |
+
elif action == "clipboard_write":
|
| 873 |
+
text = data.get("text", "")
|
| 874 |
+
try:
|
| 875 |
+
proc = subprocess.Popen(
|
| 876 |
+
["xclip", "-selection", "clipboard"],
|
| 877 |
+
stdin=subprocess.PIPE,
|
| 878 |
+
env={**os.environ, "DISPLAY": DISPLAY}
|
| 879 |
+
)
|
| 880 |
+
proc.communicate(text.encode("utf-8"))
|
| 881 |
+
await send({"type": "ack", "action": "clipboard_write", "length": len(text)})
|
| 882 |
+
except Exception as e:
|
| 883 |
+
await send({"type": "error", "action": "clipboard_write", "msg": str(e)})
|
| 884 |
+
|
| 885 |
+
# โโ clipboard_read โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 886 |
+
elif action == "clipboard_read":
|
| 887 |
+
res = run_raw_command("xclip -selection clipboard -o", timeout=5)
|
| 888 |
+
await send({"type": "clipboard_content", "text": res["stdout"]})
|
| 889 |
+
|
| 890 |
+
# โโ scroll โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 891 |
+
elif action == "scroll":
|
| 892 |
+
x, y = int(data.get("x", 0)), int(data.get("y", 0))
|
| 893 |
+
clicks = int(data.get("clicks", 3))
|
| 894 |
+
btn = "4" if clicks > 0 else "5"
|
| 895 |
+
xdo(["mousemove", str(x), str(y)])
|
| 896 |
+
for _ in range(abs(clicks)):
|
| 897 |
+
xdo(["click", btn])
|
| 898 |
+
await asyncio.sleep(0.03)
|
| 899 |
+
await send({"type": "ack", "action": "scroll", "clicks": clicks})
|
| 900 |
+
await auto_shot_grid("ุจุนุฏ ุงูุชู
ุฑูุฑ", delay=0.4)
|
| 901 |
+
|
| 902 |
+
# โโ open_app โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 903 |
+
elif action == "open_app":
|
| 904 |
+
app_cmd = data.get("cmd", "")
|
| 905 |
+
if app_cmd:
|
| 906 |
+
fixed_cmd = app_cmd
|
| 907 |
+
# ุฅุตูุงุญ firefox-esr
|
| 908 |
+
if "firefox-esr" in app_cmd:
|
| 909 |
+
esr_check = subprocess.run(["which", "firefox-esr"], capture_output=True, text=True)
|
| 910 |
+
if esr_check.returncode != 0:
|
| 911 |
+
fixed_cmd = app_cmd.replace("firefox-esr", BROWSER)
|
| 912 |
+
|
| 913 |
+
subprocess.Popen(fixed_cmd, shell=True,
|
| 914 |
+
env={**os.environ, "DISPLAY": DISPLAY},
|
| 915 |
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 916 |
+
await send({"type": "ack", "action": "open_app", "cmd": fixed_cmd})
|
| 917 |
+
|
| 918 |
+
wait_time = 7.0 if any(x in app_cmd for x in ["firefox", "chromium", "chrome"]) else 4.0
|
| 919 |
+
await asyncio.sleep(2.0)
|
| 920 |
+
await auto_shot_grid(f"ูุชุญ: {fixed_cmd[:40]} (2s)", delay=0)
|
| 921 |
+
await asyncio.sleep(wait_time - 2.0)
|
| 922 |
+
await auto_shot_grid(f"ุจุนุฏ ุชุญู
ูู: {fixed_cmd[:40]}", delay=0)
|
| 923 |
+
else:
|
| 924 |
+
await send({"type": "ack", "action": "open_app"})
|
| 925 |
+
|
| 926 |
+
# โโ open_browser โ ูุชุญ ุงูู
ุชุตูุญ โโโโโโโโโโโโโโโโโโโโ
|
| 927 |
+
elif action == "open_browser":
|
| 928 |
+
url = data.get("url", "")
|
| 929 |
+
if not url:
|
| 930 |
+
url = "about:blank"
|
| 931 |
+
browser_result = open_browser_smart(url)
|
| 932 |
+
await send({"type": "ack", "action": "open_browser", "result": browser_result})
|
| 933 |
+
await asyncio.sleep(2.0)
|
| 934 |
+
await auto_shot_grid(f"ูุชุญ ุงูู
ุชุตูุญ: {url[:50]}", delay=0)
|
| 935 |
+
await asyncio.sleep(5.0)
|
| 936 |
+
await auto_shot_grid("ุจุนุฏ ุชุญู
ูู ุงูู
ุชุตูุญ", delay=0)
|
| 937 |
+
|
| 938 |
+
# โโ mouse_drag โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 939 |
+
elif action == "mouse_drag":
|
| 940 |
+
x1, y1 = int(data.get("x1", 0)), int(data.get("y1", 0))
|
| 941 |
+
x2, y2 = int(data.get("x2", 0)), int(data.get("y2", 0))
|
| 942 |
+
xdo(["mousemove", str(x1), str(y1)])
|
| 943 |
+
xdo(["mousedown", "1"])
|
| 944 |
+
await asyncio.sleep(0.1)
|
| 945 |
+
xdo(["mousemove", str(x2), str(y2)])
|
| 946 |
+
await asyncio.sleep(0.1)
|
| 947 |
+
xdo(["mouseup", "1"])
|
| 948 |
+
await send({"type": "ack", "action": "mouse_drag"})
|
| 949 |
+
# cursor ููุชูู ุนูุฏ x2,y2
|
| 950 |
+
await auto_shot_grid("ุจุนุฏ ุงูุณุญุจ", delay=0.4, force_mx=x2, force_my=y2)
|
| 951 |
+
|
| 952 |
+
# โโ start_stream โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 953 |
+
elif action == "start_stream":
|
| 954 |
+
global stream_active, stream_fps, stream_quality, stream_scale
|
| 955 |
+
stream_fps = int(data.get("fps", 3))
|
| 956 |
+
stream_quality = int(data.get("quality", 60))
|
| 957 |
+
stream_scale = float(data.get("scale", 0.5))
|
| 958 |
+
if not stream_active:
|
| 959 |
+
stream_active = True
|
| 960 |
+
asyncio.create_task(screen_stream_loop())
|
| 961 |
+
await send({"type": "ack", "action": "start_stream"})
|
| 962 |
+
|
| 963 |
+
# โโ stop_stream โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 964 |
+
elif action == "stop_stream":
|
| 965 |
+
stream_active = False
|
| 966 |
+
await send({"type": "ack", "action": "stop_stream"})
|
| 967 |
+
|
| 968 |
+
# โโ screen_info โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 969 |
+
elif action == "screen_info":
|
| 970 |
+
w, h = _get_screen_size()
|
| 971 |
+
mx, my = _get_mouse_pos()
|
| 972 |
+
await send({
|
| 973 |
+
"type": "screen_info",
|
| 974 |
+
"width": w, "height": h,
|
| 975 |
+
"mouse_x": mx, "mouse_y": my,
|
| 976 |
+
"browser": BROWSER,
|
| 977 |
+
})
|
| 978 |
+
|
| 979 |
+
# โโ paste โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 980 |
+
elif action == "paste":
|
| 981 |
+
text = data.get("text", "")
|
| 982 |
+
if text:
|
| 983 |
+
proc = subprocess.Popen(
|
| 984 |
+
["xclip", "-selection", "clipboard"],
|
| 985 |
+
stdin=subprocess.PIPE,
|
| 986 |
+
env={**os.environ, "DISPLAY": DISPLAY}
|
| 987 |
+
)
|
| 988 |
+
proc.communicate(text.encode("utf-8"))
|
| 989 |
+
await asyncio.sleep(0.1)
|
| 990 |
+
xdo(["key", "--clearmodifiers", "ctrl+v"])
|
| 991 |
+
await send({"type": "ack", "action": "paste"})
|
| 992 |
+
await auto_shot_grid("ุจุนุฏ ุงููุตู", delay=0.5)
|
| 993 |
+
|
| 994 |
+
# โโ unknown โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 995 |
+
else:
|
| 996 |
+
await send({
|
| 997 |
+
"type": "error",
|
| 998 |
+
"msg": f"Unknown action: '{action}'. Available: screenshot, terminal, mouse_move, mouse_click, mouse_drag, keyboard_type, keyboard_hotkey, keyboard_press, clipboard_write, clipboard_read, scroll, open_app, open_browser, paste, simple_command, screen_info, start_stream, stop_stream"
|
| 999 |
+
})
|
| 1000 |
+
|
| 1001 |
+
|
| 1002 |
+
# โโโ WebSocket โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1003 |
+
|
| 1004 |
+
@app.websocket("/ws")
|
| 1005 |
+
async def websocket_endpoint(ws: WebSocket):
|
| 1006 |
+
await ws.accept()
|
| 1007 |
+
active_connections.append(ws)
|
| 1008 |
+
w, h = _get_screen_size()
|
| 1009 |
+
await ws.send_text(json.dumps({
|
| 1010 |
+
"type": "connected",
|
| 1011 |
+
"screen_width": w,
|
| 1012 |
+
"screen_height": h,
|
| 1013 |
+
"browser": BROWSER,
|
| 1014 |
+
"msg": f"Z Computer Mode v7 โ Smart Control | Browser: {BROWSER} | Screen: {w}x{h}",
|
| 1015 |
+
"commands": [
|
| 1016 |
+
"screenshot", "terminal {cmd}", "mouse_move {x,y}", "mouse_click {x,y,button}",
|
| 1017 |
+
"keyboard_type {text}", "keyboard_hotkey {keys}", "scroll {x,y,clicks}",
|
| 1018 |
+
"open_app {cmd}", "open_browser {url}", "simple_command {cmd}",
|
| 1019 |
+
"Simple cmds: 'open firefox', 'mouse go x y', 'click x y', 'type TEXT', 'key enter'"
|
| 1020 |
+
]
|
| 1021 |
+
}, ensure_ascii=False))
|
| 1022 |
+
# ุฃุฑุณู screenshot ุฃูููุฉ ู
ุน grid
|
| 1023 |
+
result = capture_screen_with_grid(scale=0.65, quality=72)
|
| 1024 |
+
if result["data"]:
|
| 1025 |
+
await ws.send_text(json.dumps({
|
| 1026 |
+
"type": "screenshot",
|
| 1027 |
+
"data": result["data"],
|
| 1028 |
+
"ts": int(time.time() * 1000),
|
| 1029 |
+
"label": "ุงูุดุงุดุฉ ุงูุฃูููุฉ",
|
| 1030 |
+
"screen_width": result["width"],
|
| 1031 |
+
"screen_height": result["height"],
|
| 1032 |
+
"mouse_x": result["mouse_x"],
|
| 1033 |
+
"mouse_y": result["mouse_y"],
|
| 1034 |
+
"has_grid": True,
|
| 1035 |
+
}, ensure_ascii=False))
|
| 1036 |
+
try:
|
| 1037 |
+
while True:
|
| 1038 |
+
raw = await ws.receive_text()
|
| 1039 |
+
await handle_action(ws, json.loads(raw))
|
| 1040 |
+
except WebSocketDisconnect:
|
| 1041 |
+
pass
|
| 1042 |
+
except Exception as e:
|
| 1043 |
+
print(f"[ws] {e}")
|
| 1044 |
+
finally:
|
| 1045 |
+
if ws in active_connections:
|
| 1046 |
+
active_connections.remove(ws)
|
| 1047 |
+
|
| 1048 |
+
|
| 1049 |
+
# โโโ REST โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1050 |
+
|
| 1051 |
+
@app.get("/screenshot")
|
| 1052 |
+
async def rest_screenshot():
|
| 1053 |
+
result = capture_screen_with_grid(scale=0.7, quality=73)
|
| 1054 |
+
return JSONResponse({
|
| 1055 |
+
"image": result["data"],
|
| 1056 |
+
"ts": int(time.time() * 1000),
|
| 1057 |
+
"screen_width": result["width"],
|
| 1058 |
+
"screen_height": result["height"],
|
| 1059 |
+
"mouse_x": result["mouse_x"],
|
| 1060 |
+
"mouse_y": result["mouse_y"],
|
| 1061 |
+
"has_grid": True,
|
| 1062 |
+
})
|
| 1063 |
+
|
| 1064 |
+
@app.get("/screenshot/clean")
|
| 1065 |
+
async def rest_screenshot_clean():
|
| 1066 |
+
return JSONResponse({"image": capture_screen(0.75, 75), "ts": int(time.time() * 1000)})
|
| 1067 |
+
|
| 1068 |
+
@app.post("/terminal")
|
| 1069 |
+
async def rest_terminal(body: dict):
|
| 1070 |
+
return JSONResponse(run_command_smart(body.get("cmd", ""), body.get("timeout", 60)))
|
| 1071 |
+
|
| 1072 |
+
@app.post("/simple")
|
| 1073 |
+
async def rest_simple(body: dict):
|
| 1074 |
+
"""ุชูููุฐ ุฃู
ุฑ ุจุณูุท ุนุจุฑ REST"""
|
| 1075 |
+
raw = body.get("cmd", "").strip()
|
| 1076 |
+
parsed = parse_simple_command(raw)
|
| 1077 |
+
if not parsed:
|
| 1078 |
+
return JSONResponse({"ok": False, "error": f"Unknown simple command: {raw}"}, status_code=400)
|
| 1079 |
+
return JSONResponse({"ok": True, "parsed": parsed})
|
| 1080 |
+
|
| 1081 |
+
@app.get("/health")
|
| 1082 |
+
async def health():
|
| 1083 |
+
w, h = _get_screen_size()
|
| 1084 |
+
mx, my = _get_mouse_pos()
|
| 1085 |
+
browser_check = subprocess.run(["which", "firefox-esr"], capture_output=True, text=True)
|
| 1086 |
+
return {
|
| 1087 |
+
"status": "ok",
|
| 1088 |
+
"version": "v7-smart-control",
|
| 1089 |
+
"browser": BROWSER,
|
| 1090 |
+
"firefox_esr_available": browser_check.returncode == 0,
|
| 1091 |
+
"screen_width": w,
|
| 1092 |
+
"screen_height": h,
|
| 1093 |
+
"mouse_x": mx,
|
| 1094 |
+
"mouse_y": my,
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
@app.get("/search/{query}")
|
| 1098 |
+
async def quick_search(query: str):
|
| 1099 |
+
res = run_command_smart(
|
| 1100 |
+
f"curl -s --max-time 15 'https://api.duckduckgo.com/?q={urllib.parse.quote_plus(query)}&format=json&no_html=1'",
|
| 1101 |
+
timeout=30
|
| 1102 |
+
)
|
| 1103 |
+
return JSONResponse(res)
|
| 1104 |
+
|
| 1105 |
+
|
| 1106 |
+
if __name__ == "__main__":
|
| 1107 |
+
port = int(os.environ.get("PORT", 7860))
|
| 1108 |
+
uvicorn.run("app:app", host="0.0.0.0", port=port, log_level="info")
|
| 1109 |
+
|
| 1110 |
+
|
| 1111 |
+
|
gitattributes (28).txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
requirements (26).txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.111.0
|
| 2 |
+
uvicorn[standard]==0.29.0
|
| 3 |
+
websockets==12.0
|
| 4 |
+
Pillow==10.3.0
|
| 5 |
+
python-multipart==0.0.9
|
start (11).sh
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 3 |
+
# THE Z AI โ Computer Mode Startup (FIXED v4)
|
| 4 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 5 |
+
|
| 6 |
+
echo "===== Application Startup at $(date '+%Y-%m-%d %H:%M:%S') ====="
|
| 7 |
+
echo "๐ฅ๏ธ Starting Z Computer Mode Server v4..."
|
| 8 |
+
|
| 9 |
+
# โโ 1. Start Virtual Display โโโโโโโโโโโโโโโโโโโโโ
|
| 10 |
+
pkill Xvfb 2>/dev/null || true
|
| 11 |
+
sleep 0.5
|
| 12 |
+
Xvfb :1 -screen 0 1920x1080x24 -ac +extension GLX +render -noreset &
|
| 13 |
+
XVFB_PID=$!
|
| 14 |
+
echo "โ
Xvfb started (PID: $XVFB_PID)"
|
| 15 |
+
sleep 2
|
| 16 |
+
|
| 17 |
+
# โโ 2. Export DISPLAY โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 18 |
+
export DISPLAY=:1
|
| 19 |
+
export XAUTHORITY=/root/.Xauthority
|
| 20 |
+
touch $XAUTHORITY
|
| 21 |
+
xauth generate :1 . trusted 2>/dev/null || true
|
| 22 |
+
echo "โ
DISPLAY=$DISPLAY"
|
| 23 |
+
|
| 24 |
+
# โโ 3. Start Fluxbox WM โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 25 |
+
pkill fluxbox 2>/dev/null || true
|
| 26 |
+
fluxbox &
|
| 27 |
+
FLUX_PID=$!
|
| 28 |
+
echo "โ
Fluxbox started (PID: $FLUX_PID)"
|
| 29 |
+
sleep 3
|
| 30 |
+
|
| 31 |
+
# โโ 4. ุฎูููุฉ ู
ููููุฉ ูุงุถุญุฉ ุจุนุฏ Fluxbox โโโโโโโโโโโโ
|
| 32 |
+
# Fluxbox ูููุบู xsetroot โ ูุถุนูุง ุจุนุฏู ูููุฑุฑูุง
|
| 33 |
+
xsetroot -solid "#1a1a2e" 2>/dev/null || true
|
| 34 |
+
|
| 35 |
+
# ุงุณุชุฎุฏู
convert (ImageMagick) ูุฑุณู
ุฎูููุฉ ู
ููููุฉ ู
ุน ูุต ูุงุถุญ
|
| 36 |
+
# ุญุชู ูุนุฑู ุงูู AI ุฃู ุงูุดุงุดุฉ ุญูุฉ ูููุณุช ุณูุฏุงุก
|
| 37 |
+
convert -size 1920x1080 \
|
| 38 |
+
gradient:"#1a1a2e-#16213e" \
|
| 39 |
+
-fill "#00d4aa" \
|
| 40 |
+
-pointsize 48 \
|
| 41 |
+
-gravity Center \
|
| 42 |
+
-annotate 0 "Z AI Computer Mode โ Ready" \
|
| 43 |
+
-fill "#4a90d9" \
|
| 44 |
+
-pointsize 28 \
|
| 45 |
+
-gravity South \
|
| 46 |
+
-annotate 0 "Screen: 1920x1080 | DISPLAY=:1 | $(date '+%H:%M:%S')" \
|
| 47 |
+
/tmp/wallpaper.png 2>/dev/null && \
|
| 48 |
+
feh --bg-fill /tmp/wallpaper.png 2>/dev/null || \
|
| 49 |
+
xsetroot -solid "#1a1a2e" 2>/dev/null || true
|
| 50 |
+
|
| 51 |
+
echo "โ
Wallpaper set"
|
| 52 |
+
|
| 53 |
+
# โโ 5. Verify display works โโโโโโโโโโโโโโโโโโโโโโ
|
| 54 |
+
echo "๐ Testing display..."
|
| 55 |
+
if xdotool getdisplaygeometry 2>/dev/null; then
|
| 56 |
+
echo "โ
Display working!"
|
| 57 |
+
else
|
| 58 |
+
echo "โ ๏ธ xdotool test failed โ continuing anyway"
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
# โโ 6. Test screenshot โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 62 |
+
scrot -q 80 /tmp/test_shot.png 2>/dev/null && \
|
| 63 |
+
echo "โ
Screenshot test: OK" || \
|
| 64 |
+
echo "โ ๏ธ scrot test failed"
|
| 65 |
+
|
| 66 |
+
# โโ 7. Detect browser โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 67 |
+
if command -v firefox-esr &>/dev/null; then
|
| 68 |
+
echo "โ
Browser: firefox-esr"
|
| 69 |
+
BROWSER="firefox-esr"
|
| 70 |
+
elif command -v firefox &>/dev/null; then
|
| 71 |
+
echo "โ
Browser: firefox"
|
| 72 |
+
BROWSER="firefox"
|
| 73 |
+
elif command -v chromium-browser &>/dev/null; then
|
| 74 |
+
echo "โ
Browser: chromium-browser"
|
| 75 |
+
BROWSER="chromium-browser"
|
| 76 |
+
else
|
| 77 |
+
echo "โ ๏ธ No browser found"
|
| 78 |
+
BROWSER=""
|
| 79 |
+
fi
|
| 80 |
+
export DEFAULT_BROWSER="$BROWSER"
|
| 81 |
+
|
| 82 |
+
# โโ 8. Pre-warm Firefox (ุงุฎุชูุงุฑู โ ูุณุฑูุน ุฃูู ูุชุญ) โ
|
| 83 |
+
if [ -n "$BROWSER" ]; then
|
| 84 |
+
$BROWSER --headless --no-sandbox about:blank &>/dev/null &
|
| 85 |
+
sleep 2
|
| 86 |
+
pkill -f "headless" 2>/dev/null || true
|
| 87 |
+
echo "โ
Browser pre-warmed"
|
| 88 |
+
fi
|
| 89 |
+
|
| 90 |
+
echo "๐ Starting Python API on port ${PORT:-7860}..."
|
| 91 |
+
cd /app
|
| 92 |
+
exec python3 app.py
|