Skip to content

Commit d7d59ab

Browse files
pydisplay: Release v0.0.26 (380c2e8).
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent f3002c9 commit d7d59ab

13 files changed

Lines changed: 344 additions & 102 deletions

File tree

micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ def _mp_native(f):
2727

2828

2929
def _install_byteswap():
30-
"""Prefer GitHub ``add_ons/byteswap`` (numpy/ulab/viper); else portable Python.
30+
"""Prefer GitHub ``utils/byteswap`` (numpy/ulab/viper); else portable Python.
3131
3232
No in-tree ``@viper`` here: micropython-lib MIP packaging compiles every
3333
``.py`` with ``mpy-cross`` without ``-march``, so viper/native emitters in
3434
this package would break the index build. Fast swap on SPI boards comes
35-
from installing ``add_ons`` (GitHub MIP), not from the displaysys package.
35+
from installing ``utils`` (GitHub MIP), not from the displaysys package.
3636
"""
3737
try:
3838
from byteswap import byteswap as _byteswap_native
@@ -68,6 +68,7 @@ def byteswap(buf):
6868
"color_rgb",
6969
"desktop_work_area",
7070
"env_bool",
71+
"env_float",
7172
"env_get",
7273
"env_int",
7374
"env_set",
@@ -145,6 +146,17 @@ def env_int(name, default=0):
145146
return int(default)
146147

147148

149+
def env_float(name, default=0.0):
150+
"""Read a floating-point environment variable portably."""
151+
raw = _env_raw(name)
152+
if raw is None:
153+
return float(default)
154+
try:
155+
return float(str(raw).strip())
156+
except (TypeError, ValueError):
157+
return float(default)
158+
159+
148160
def _env_raw(name):
149161
if name in _overrides:
150162
return _overrides[name]
@@ -560,7 +572,7 @@ def __init__(self, *, quiet=False):
560572
if _BYTESWAP_BACKEND == "pure_python":
561573
print(
562574
f"{self.__class__.__name__}: warning: slow byteswap fallback; "
563-
"install add_ons/byteswap (GitHub MIP) for viper/numpy swap"
575+
"install utils/byteswap (GitHub MIP) for viper/numpy swap"
564576
)
565577

566578
def __del__(self):

micropython/pydisplay/displaysys/displaysys/displaysys/jndisplay.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from displaysys import DisplayDriver, color_rgb
1515
from eventsys import events
16-
from eventsys.keys import Keys, default_quit_chord, key_to_keycode, mod_mask
16+
from eventsys.keys import Keys, default_quit_chord, enrich_mod, key_to_keycode, mod_mask
1717

1818
_JN_DEPS = "pip install ipywidgets ipyevents"
1919

@@ -246,7 +246,7 @@ def _on_wheel(self, event):
246246

247247
def _on_key(self, event, kind):
248248
keycode = key_to_keycode(event.get("key", ""), event.get("location", 0))
249-
mod = mod_mask(
249+
base_mod = mod_mask(
250250
event.get("ctrlKey"),
251251
event.get("shiftKey"),
252252
event.get("altKey"),
@@ -256,10 +256,10 @@ def _on_key(self, event, kind):
256256
if event.get("repeat"): # ignore auto-repeat
257257
return
258258
self._pressed.add(keycode)
259-
self._enqueue_key(events.KEYDOWN, keycode, mod)
259+
self._enqueue_key(events.KEYDOWN, keycode, enrich_mod(base_mod, self._pressed))
260260
else:
261261
self._pressed.discard(keycode)
262-
self._enqueue_key(events.KEYUP, keycode, mod)
262+
self._enqueue_key(events.KEYUP, keycode, enrich_mod(base_mod, self._pressed))
263263

264264
def _enqueue_key(self, type, keycode, mod):
265265
self._queue.append(events.Key(type, Keys.keyname(keycode), keycode, mod, 0, None))

micropython/pydisplay/displaysys/displaysys/displaysys/pgdisplay.py

Lines changed: 28 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,79 +20,7 @@
2020
from eventsys import events
2121
from eventsys.keys import default_quit_chord
2222

23-
__all__ = ["FFmpegFrameRecorder", "PGDisplay", "get_events", "poll_event"]
24-
25-
26-
class FFmpegFrameRecorder:
27-
"""Pipe fixed-size RGB24 frames to ffmpeg for MP4 output."""
28-
29-
__slots__ = ("_closed", "_frame_bytes", "_frames", "_proc", "fps", "height", "path", "width")
30-
31-
def __init__(self, path, width, height, fps=12):
32-
import subprocess
33-
34-
self.path = path
35-
self.width = width
36-
self.height = height
37-
self.fps = fps
38-
self._frames = 0
39-
self._closed = False
40-
self._frame_bytes = width * height * 3
41-
self._proc = subprocess.Popen(
42-
[
43-
"ffmpeg",
44-
"-y",
45-
"-f",
46-
"rawvideo",
47-
"-pix_fmt",
48-
"rgb24",
49-
"-s",
50-
f"{width}x{height}",
51-
"-r",
52-
str(fps),
53-
"-i",
54-
"pipe:0",
55-
"-an",
56-
"-c:v",
57-
"libx264",
58-
"-pix_fmt",
59-
"yuv420p",
60-
path,
61-
],
62-
stdin=subprocess.PIPE,
63-
stdout=subprocess.DEVNULL,
64-
stderr=subprocess.PIPE,
65-
)
66-
67-
def write(self, rgb_bytes):
68-
if self._closed:
69-
return
70-
if len(rgb_bytes) != self._frame_bytes:
71-
raise ValueError(
72-
f"frame size {len(rgb_bytes)} != expected {self._frame_bytes} "
73-
f"for {self.width}x{self.height} RGB24"
74-
)
75-
self._proc.stdin.write(rgb_bytes)
76-
self._frames += 1
77-
78-
def close(self):
79-
if self._closed:
80-
return self._frames
81-
self._closed = True
82-
try:
83-
self._proc.stdin.close()
84-
except Exception:
85-
pass
86-
err = self._proc.stderr.read().decode("utf-8", errors="replace")
87-
try:
88-
self._proc.stderr.close()
89-
except Exception:
90-
pass
91-
rc = self._proc.wait()
92-
if rc != 0:
93-
tail = "\n".join(err.strip().splitlines()[-8:])
94-
raise RuntimeError(f"ffmpeg exited {rc} for {self.path}:\n{tail}")
95-
return self._frames
23+
__all__ = ["PGDisplay", "get_events", "poll_event"]
9624

9725

9826
def _pg_key_name(key):
@@ -170,7 +98,7 @@ def _handle_window_close(window):
17098
return None
17199

172100

173-
def _convert(e):
101+
def _convert(e): # noqa: PLR0911 — one return per pygame event type
174102
"""Convert a pygame event to an eventsys namedtuple."""
175103
t = e.type
176104
if t == pg.QUIT:
@@ -215,6 +143,9 @@ def _convert(e):
215143
win,
216144
)
217145
if t in (pg.KEYDOWN, pg.KEYUP):
146+
# Match browser / SDL backends: drop OS auto-repeat KEYDOWNs.
147+
if t == pg.KEYDOWN and getattr(e, "repeat", False):
148+
return None
218149
return events.Key(t, _pg_key_name(e.key), e.key, e.mod, getattr(e, "scancode", 0), win)
219150
if t == pg.JOYAXISMOTION:
220151
return events.JoyAxisMotion(t, e.instance_id, e.axis, e.value / 32767.0)
@@ -522,11 +453,19 @@ def _video_active(self) -> bool:
522453
except pg.error:
523454
return False
524455

525-
def _buffer_rgb(self) -> bytes:
526-
"""Export the logical framebuffer as packed RGB24 bytes."""
527-
if hasattr(pg.image, "tostring"):
528-
return pg.image.tostring(self._buffer, "RGB")
529-
return pg.image.tobytes(self._buffer, "RGB")
456+
def capture_rgb(self):
457+
"""Return the visible window as ``(RGB24 bytes, width, height)``."""
458+
if not self._video_active():
459+
raise RuntimeError("pygame display is not active")
460+
self.show()
461+
width, height = self._window.get_size()
462+
return self._window_rgb(), width, height
463+
464+
def _window_rgb(self):
465+
"""Export the composed visible window as packed RGB24 bytes."""
466+
if hasattr(pg.image, "tobytes"):
467+
return pg.image.tobytes(self._window, "RGB")
468+
return pg.image.tostring(self._window, "RGB")
530469

531470
@property
532471
def frame_recording(self) -> bool:
@@ -535,9 +474,11 @@ def frame_recording(self) -> bool:
535474

536475
def open_frame_recorder(self, path, *, fps=12, width=None, height=None):
537476
"""Attach an ffmpeg-backed recorder that receives one RGB24 frame per ``show()``."""
477+
from frame_recorder import FFmpegFrameRecorder
478+
538479
self.close_frame_recorder()
539-
w = self.width if width is None else width
540-
h = self.height if height is None else height
480+
w = int(self.width * self._scale) if width is None else width
481+
h = int(self.height * self._scale) if height is None else height
541482
self._frame_recorder = FFmpegFrameRecorder(path, w, h, fps)
542483
return self._frame_recorder
543484

@@ -546,11 +487,13 @@ def close_frame_recorder(self):
546487
recorder = self._frame_recorder
547488
self._frame_recorder = None
548489
if recorder is not None:
549-
recorder.close()
490+
return recorder.close()
491+
return 0
550492

551493
def _record_frame(self, rgb_bytes) -> None:
552-
if self._frame_recorder is not None:
553-
self._frame_recorder.write(rgb_bytes)
494+
recorder = self._frame_recorder
495+
if recorder is not None:
496+
recorder.write(rgb_bytes)
554497

555498
def render(self, renderRect=None) -> None:
556499
"""
@@ -604,7 +547,7 @@ def show(self, _timer=None) -> None:
604547
self.render()
605548
self._render_dirty = False
606549
if self._frame_recorder is not None:
607-
self._record_frame(self._buffer_rgb())
550+
self._record_frame(self._window_rgb())
608551
try:
609552
self._pg_window.flip()
610553
except pg.error:

micropython/pydisplay/displaysys/displaysys/displaysys/psdisplay.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111

1212
from displaysys import DisplayDriver, color_rgb
1313
from eventsys import events
14-
from eventsys.keys import Keys, default_quit_chord, dom_key_scrolls_page, key_to_keycode, mod_mask
14+
from eventsys.keys import (
15+
Keys,
16+
default_quit_chord,
17+
dom_key_scrolls_page,
18+
enrich_mod,
19+
key_to_keycode,
20+
mod_mask,
21+
)
1522

1623
try: # Gamepad polling is optional and only available in a browser.
1724
from js import navigator
@@ -254,18 +261,18 @@ def _suppress_browser_scroll(self, e, keycode):
254261

255262
def _on_keydown(self, e):
256263
keycode = key_to_keycode(e.key, e.location)
257-
mod = mod_mask(e.ctrlKey, e.shiftKey, e.altKey, e.metaKey)
258264
self._suppress_browser_scroll(e, keycode)
259265
if e.repeat: # ignore auto-repeat
260266
return
261267
self._pressed.add(keycode)
268+
mod = enrich_mod(mod_mask(e.ctrlKey, e.shiftKey, e.altKey, e.metaKey), self._pressed)
262269
self._enqueue_key(events.KEYDOWN, keycode, mod)
263270

264271
def _on_keyup(self, e):
265272
keycode = key_to_keycode(e.key, e.location)
266-
mod = mod_mask(e.ctrlKey, e.shiftKey, e.altKey, e.metaKey)
267273
self._suppress_browser_scroll(e, keycode)
268274
self._pressed.discard(keycode)
275+
mod = enrich_mod(mod_mask(e.ctrlKey, e.shiftKey, e.altKey, e.metaKey), self._pressed)
269276
self._enqueue_key(events.KEYUP, keycode, mod)
270277

271278
############### Gamepad ################

0 commit comments

Comments
 (0)