Skip to content

Commit 1d3c4ec

Browse files
WebServer: add basic "View Screen" functionality to view the device's display remotely
1 parent c098fbf commit 1d3c4ec

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Frameworks:
1111
- Add mpos.ui.change_task_handler() for improving IR timing accuracy
1212
- AppearanceManager: fix set_light_mode() and set_primary_color()
1313
- SharedPreferences: don't print values on serial/REPL
14+
- WebServer: add basic "View Screen" functionality to view the device's display remotely
1415

1516
OS:
1617
- LilyGo T-Watch S3 Plus: add support for IR Remote app

internal_filesystem/builtin/html/webrepl_inlined_minified.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ <h1>MicroPythonOS WebREPL</h1>
3535

3636
<div class="file-box" id="file-status"><span style="color:#707070">(file operation status)</span></div>
3737

38+
<div class="file-box">
39+
<input type="button" value="View Screen" onclick="window.open('/screenshot.bmp', '_blank'); return false;" />
40+
</div>
41+
3842
</div>
3943

4044
<br clear="both" />

internal_filesystem/lib/mpos/webserver/webrepl_http.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import os
22
import socket
33
import uio
4+
import struct
45

56
import _webrepl
67
from . import webrepl
78
import websocket
9+
import lvgl as lv
10+
11+
from mpos.ui.display_metrics import DisplayMetrics
812

913
WEBREPL_HTML_PATH = "builtin/html/webrepl_inlined_minified.html"
1014
'''
@@ -80,6 +84,59 @@ def _send_response(cl, status, content_type, body):
8084
cl.close()
8185

8286

87+
def _build_bmp_header(width, height, pixel_data_size):
88+
bmp_header_size = 54
89+
file_size = bmp_header_size + pixel_data_size
90+
header = bytearray(bmp_header_size)
91+
header[0:2] = b"BM"
92+
header[2:6] = struct.pack("<I", file_size)
93+
header[10:14] = struct.pack("<I", bmp_header_size)
94+
header[14:18] = struct.pack("<I", 40)
95+
header[18:22] = struct.pack("<I", width)
96+
header[22:26] = struct.pack("<i", -height)
97+
header[26:28] = struct.pack("<H", 1)
98+
header[28:30] = struct.pack("<H", 24)
99+
header[30:34] = struct.pack("<I", 0)
100+
header[34:38] = struct.pack("<I", pixel_data_size)
101+
return header
102+
103+
104+
def _snapshot_to_bmp():
105+
width = DisplayMetrics.width()
106+
height = DisplayMetrics.height()
107+
rgb_size = width * height * 3
108+
row_stride = ((width * 3 + 3) // 4) * 4
109+
pixel_data_size = row_stride * height
110+
111+
rgb_buffer = bytearray(rgb_size)
112+
image_dsc = lv.image_dsc_t()
113+
lv.snapshot_take_to_buf(
114+
lv.screen_active(),
115+
lv.COLOR_FORMAT.RGB888,
116+
image_dsc,
117+
rgb_buffer,
118+
rgb_size,
119+
)
120+
121+
bmp = bytearray(54 + pixel_data_size)
122+
bmp[0:54] = _build_bmp_header(width, height, pixel_data_size)
123+
124+
view = memoryview(bmp)[54:]
125+
if row_stride == width * 3:
126+
view[:rgb_size] = rgb_buffer
127+
else:
128+
for y in range(height):
129+
src_start = y * width * 3
130+
src_end = src_start + width * 3
131+
dest_start = y * row_stride
132+
view[dest_start : dest_start + width * 3] = rgb_buffer[src_start:src_end]
133+
134+
for i in range(0, rgb_size, 3):
135+
view[i], view[i + 2] = view[i + 2], view[i]
136+
137+
return bmp
138+
139+
83140
def _send_file_response(cl, path, content_type):
84141
try:
85142
with open(path, "rb") as handle:
@@ -125,6 +182,11 @@ def accept_handler(listen_sock):
125182
asset_path, content_type = WEBREPL_ASSETS[path]
126183
return _send_file_response(cl, asset_path, content_type)
127184

185+
if path == b"/screenshot.bmp":
186+
bmp = _snapshot_to_bmp()
187+
_send_response(cl, b"200 OK", b"image/bmp", bmp)
188+
return False
189+
128190
_send_response(cl, b"404 Not Found", b"text/plain", b"Not Found")
129191
return False
130192
except Exception as exc:

0 commit comments

Comments
 (0)