Update app.py
Browse files
app.py
CHANGED
|
@@ -1,19 +1,25 @@
|
|
| 1 |
import os
|
| 2 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import subprocess
|
| 4 |
-
import
|
| 5 |
-
from
|
| 6 |
import pyautogui
|
| 7 |
|
| 8 |
-
# Set Display explicitly for PyAutoGUI
|
| 9 |
-
os.environ["DISPLAY"] = ":99"
|
| 10 |
-
|
| 11 |
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
# PyAutoGUI Failsafe Disable
|
| 14 |
pyautogui.FAILSAFE = False
|
|
|
|
| 15 |
|
| 16 |
-
#
|
| 17 |
HTML_TEMPLATE = """
|
| 18 |
<!DOCTYPE html>
|
| 19 |
<html>
|
|
@@ -21,119 +27,42 @@ HTML_TEMPLATE = """
|
|
| 21 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 22 |
<title>Cloud PC</title>
|
| 23 |
<style>
|
| 24 |
-
body { margin: 0; background: #111; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
.
|
| 28 |
-
|
| 29 |
-
overflow: hidden; background: black; position: relative;
|
| 30 |
-
}
|
| 31 |
-
#desktop {
|
| 32 |
-
width: 100%; height: auto; max-height: 100%;
|
| 33 |
-
object-fit: contain; cursor: pointer;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
/* FLOATING TOOLBAR */
|
| 37 |
-
.toolbar {
|
| 38 |
-
position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
|
| 39 |
-
background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px);
|
| 40 |
-
padding: 10px; border-radius: 30px; display: flex; gap: 15px;
|
| 41 |
-
border: 1px solid rgba(255,255,255,0.2);
|
| 42 |
-
}
|
| 43 |
-
.btn {
|
| 44 |
-
width: 40px; height: 40px; border-radius: 50%; background: #333; color: white;
|
| 45 |
-
display: flex; align-items: center; justify-content: center; font-size: 18px;
|
| 46 |
-
cursor: pointer; border: none;
|
| 47 |
-
}
|
| 48 |
-
.btn:active { background: #2563eb; }
|
| 49 |
-
|
| 50 |
-
/* KEYBOARD INPUT */
|
| 51 |
-
#kbdBar {
|
| 52 |
-
display: none; padding: 10px; background: #222; border-top: 1px solid #444;
|
| 53 |
-
}
|
| 54 |
-
input { width: 100%; padding: 10px; background: #333; border: none; color: white; border-radius: 5px; }
|
| 55 |
</style>
|
| 56 |
</head>
|
| 57 |
<body>
|
| 58 |
-
|
| 59 |
<div class="screen-container">
|
| 60 |
<img id="desktop" draggable="false">
|
| 61 |
-
|
| 62 |
<div class="toolbar">
|
| 63 |
-
<
|
| 64 |
-
<
|
| 65 |
-
<
|
| 66 |
-
<button class="btn" onclick="launchApp('terminal')">_></button>
|
| 67 |
-
<button class="btn" onclick="launchApp('chrome')">🌐</button>
|
| 68 |
</div>
|
| 69 |
</div>
|
| 70 |
-
|
| 71 |
-
<div id="kbdBar">
|
| 72 |
-
<input type="text" id="typeInput" placeholder="Type here..." autocomplete="off">
|
| 73 |
-
<button onclick="sendText()" style="padding: 10px; width: 100%; margin-top: 5px; background: blue; color: white; border: none; border-radius: 5px;">Send</button>
|
| 74 |
-
</div>
|
| 75 |
-
|
| 76 |
<script>
|
| 77 |
const img = document.getElementById('desktop');
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
function refresh() {
|
| 81 |
-
img.src = '/snapshot?' + new Date().getTime();
|
| 82 |
-
}
|
| 83 |
-
setInterval(refresh, 800); // 0.8s Refresh Rate
|
| 84 |
|
| 85 |
-
// 2. Click Handler (Accurate Mapping)
|
| 86 |
img.addEventListener('click', async (e) => {
|
| 87 |
const rect = img.getBoundingClientRect();
|
| 88 |
-
// Ignore black bars
|
| 89 |
if (e.clientX < rect.left || e.clientX > rect.right) return;
|
| 90 |
-
|
| 91 |
const x = (e.clientX - rect.left) / rect.width;
|
| 92 |
const y = (e.clientY - rect.top) / rect.height;
|
| 93 |
-
|
| 94 |
-
await fetch('/interact', {
|
| 95 |
-
method: 'POST',
|
| 96 |
-
headers: {'Content-Type': 'application/json'},
|
| 97 |
-
body: JSON.stringify({action: 'tap', x: x, y: y})
|
| 98 |
-
});
|
| 99 |
refresh();
|
| 100 |
});
|
| 101 |
|
| 102 |
-
// 3. Actions
|
| 103 |
async function sendKey(k) {
|
| 104 |
-
await fetch('/interact', {
|
| 105 |
-
method: 'POST',
|
| 106 |
-
headers: {'Content-Type': 'application/json'},
|
| 107 |
-
body: JSON.stringify({action: 'key', key: k})
|
| 108 |
-
});
|
| 109 |
refresh();
|
| 110 |
}
|
| 111 |
-
|
| 112 |
async function launchApp(app) {
|
| 113 |
-
await fetch('/interact', {
|
| 114 |
-
method: 'POST',
|
| 115 |
-
headers: {'Content-Type': 'application/json'},
|
| 116 |
-
body: JSON.stringify({action: 'launch', app: app})
|
| 117 |
-
});
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
function toggleKeyboard() {
|
| 121 |
-
const bar = document.getElementById('kbdBar');
|
| 122 |
-
bar.style.display = bar.style.display === 'block' ? 'none' : 'block';
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
async function sendText() {
|
| 126 |
-
const txt = document.getElementById('typeInput').value;
|
| 127 |
-
if(!txt) return;
|
| 128 |
-
document.getElementById('typeInput').value = "";
|
| 129 |
-
document.getElementById('kbdBar').style.display = 'none';
|
| 130 |
-
|
| 131 |
-
await fetch('/interact', {
|
| 132 |
-
method: 'POST',
|
| 133 |
-
headers: {'Content-Type': 'application/json'},
|
| 134 |
-
body: JSON.stringify({action: 'type', text: txt})
|
| 135 |
-
});
|
| 136 |
-
refresh();
|
| 137 |
}
|
| 138 |
</script>
|
| 139 |
</body>
|
|
@@ -141,50 +70,27 @@ HTML_TEMPLATE = """
|
|
| 141 |
"""
|
| 142 |
|
| 143 |
@app.route('/')
|
| 144 |
-
def home():
|
| 145 |
-
return HTML_TEMPLATE
|
| 146 |
|
| 147 |
-
@app.route('/snapshot')
|
| 148 |
-
def
|
| 149 |
try:
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
return Response(img_data, mimetype='image/jpeg')
|
| 155 |
-
except Exception as e:
|
| 156 |
-
return str(e), 500
|
| 157 |
|
| 158 |
@app.route('/interact', methods=['POST'])
|
| 159 |
def interact():
|
| 160 |
data = request.json
|
| 161 |
action = data.get('action')
|
| 162 |
-
|
| 163 |
try:
|
| 164 |
if action == 'tap':
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
y = int(data.get('y') * 768)
|
| 168 |
pyautogui.click(x, y)
|
| 169 |
-
|
| 170 |
-
elif action == 'type':
|
| 171 |
-
pyautogui.write(data.get('text'))
|
| 172 |
-
|
| 173 |
elif action == 'key':
|
| 174 |
k = data.get('key')
|
| 175 |
if k == 'win': pyautogui.press('super')
|
| 176 |
else: pyautogui.press(k)
|
| 177 |
-
|
| 178 |
-
elif action == 'launch':
|
| 179 |
-
app_name = data.get('app')
|
| 180 |
-
if app_name == 'terminal':
|
| 181 |
-
subprocess.Popen("xfce4-terminal &", shell=True)
|
| 182 |
-
elif app_name == 'chrome':
|
| 183 |
-
subprocess.Popen("chromium-browser --no-sandbox &", shell=True)
|
| 184 |
-
|
| 185 |
-
return "OK"
|
| 186 |
-
except Exception as e:
|
| 187 |
-
return str(e), 500
|
| 188 |
-
|
| 189 |
-
if __name__ == '__main__':
|
| 190 |
-
app.run(host='0.0.0.0', port=7860, threaded=True)
|
|
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
+
# Set Display FIRST
|
| 4 |
+
os.environ["DISPLAY"] = ":99"
|
| 5 |
+
os.environ["XAUTHORITY"] = "/root/.Xauthority"
|
| 6 |
+
|
| 7 |
+
import base64
|
| 8 |
import subprocess
|
| 9 |
+
from flask import Flask, request, jsonify
|
| 10 |
+
from flask_cors import CORS
|
| 11 |
import pyautogui
|
| 12 |
|
|
|
|
|
|
|
|
|
|
| 13 |
app = Flask(__name__)
|
| 14 |
+
CORS(app)
|
| 15 |
+
|
| 16 |
+
SCREEN_WIDTH = 1024
|
| 17 |
+
SCREEN_HEIGHT = 768
|
| 18 |
|
|
|
|
| 19 |
pyautogui.FAILSAFE = False
|
| 20 |
+
pyautogui.PAUSE = 0.1
|
| 21 |
|
| 22 |
+
# EMBEDDED HTML UI
|
| 23 |
HTML_TEMPLATE = """
|
| 24 |
<!DOCTYPE html>
|
| 25 |
<html>
|
|
|
|
| 27 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 28 |
<title>Cloud PC</title>
|
| 29 |
<style>
|
| 30 |
+
body { margin: 0; background: #111; height: 100vh; display: flex; flex-direction: column; overflow: hidden; user-select: none; }
|
| 31 |
+
.screen-container { flex: 1; display: flex; justify-content: center; background: black; }
|
| 32 |
+
#desktop { width: 100%; height: 100%; object-fit: contain; }
|
| 33 |
+
.toolbar { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(255,255,255,0.2); padding: 10px; border-radius: 30px; display: flex; gap: 15px; }
|
| 34 |
+
.btn { width: 40px; height: 40px; border-radius: 50%; background: #333; color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
</style>
|
| 36 |
</head>
|
| 37 |
<body>
|
|
|
|
| 38 |
<div class="screen-container">
|
| 39 |
<img id="desktop" draggable="false">
|
|
|
|
| 40 |
<div class="toolbar">
|
| 41 |
+
<div class="btn" onclick="sendKey('win')">⊞</div>
|
| 42 |
+
<div class="btn" onclick="sendKey('enter')">↵</div>
|
| 43 |
+
<div class="btn" onclick="launchApp('chrome')">🌐</div>
|
|
|
|
|
|
|
| 44 |
</div>
|
| 45 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
<script>
|
| 47 |
const img = document.getElementById('desktop');
|
| 48 |
+
function refresh() { img.src = '/snapshot?' + new Date().getTime(); }
|
| 49 |
+
setInterval(refresh, 800);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
|
|
|
| 51 |
img.addEventListener('click', async (e) => {
|
| 52 |
const rect = img.getBoundingClientRect();
|
|
|
|
| 53 |
if (e.clientX < rect.left || e.clientX > rect.right) return;
|
|
|
|
| 54 |
const x = (e.clientX - rect.left) / rect.width;
|
| 55 |
const y = (e.clientY - rect.top) / rect.height;
|
| 56 |
+
await fetch('/interact', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({action: 'tap', x: x, y: y}) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
refresh();
|
| 58 |
});
|
| 59 |
|
|
|
|
| 60 |
async function sendKey(k) {
|
| 61 |
+
await fetch('/interact', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({action: 'key', key: k}) });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
refresh();
|
| 63 |
}
|
|
|
|
| 64 |
async function launchApp(app) {
|
| 65 |
+
await fetch('/interact', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({action: 'launch', app: app}) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
</script>
|
| 68 |
</body>
|
|
|
|
| 70 |
"""
|
| 71 |
|
| 72 |
@app.route('/')
|
| 73 |
+
def home(): return HTML_TEMPLATE
|
|
|
|
| 74 |
|
| 75 |
+
@app.route('/snapshot', methods=['GET'])
|
| 76 |
+
def get_snapshot():
|
| 77 |
try:
|
| 78 |
+
subprocess.run(["scrot", "/tmp/screen.jpg", "-q", "20", "-o"], check=True)
|
| 79 |
+
with open("/tmp/screen.jpg", "rb") as image_file:
|
| 80 |
+
return Response(image_file.read(), mimetype='image/jpeg')
|
| 81 |
+
except: return "Error", 500
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
@app.route('/interact', methods=['POST'])
|
| 84 |
def interact():
|
| 85 |
data = request.json
|
| 86 |
action = data.get('action')
|
|
|
|
| 87 |
try:
|
| 88 |
if action == 'tap':
|
| 89 |
+
x = int(data.get('x') * SCREEN_WIDTH)
|
| 90 |
+
y = int(data.get('y') * SCREEN_HEIGHT)
|
|
|
|
| 91 |
pyautogui.click(x, y)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
elif action == 'key':
|
| 93 |
k = data.get('key')
|
| 94 |
if k == 'win': pyautogui.press('super')
|
| 95 |
else: pyautogui.press(k)
|
| 96 |
+
elif action == 'launch':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|