THEZYZSTUDIO commited on
Commit
3214f18
ยท
verified ยท
1 Parent(s): 736a5fd

Upload 5 files

Browse files
Files changed (5) hide show
  1. README (42).md +41 -0
  2. app (15) (8).py +1111 -0
  3. gitattributes (28).txt +35 -0
  4. requirements (26).txt +5 -0
  5. 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