Skip to content

Commit

Permalink
refactor: make it ready to be used for local widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
sassanh committed Oct 25, 2024
1 parent 8e4d78c commit c2017eb
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 39 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/integration_delivery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,6 @@ jobs:
- uses: actions/checkout@v4
name: Checkout

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Upcoming

- chore: migrate from poetry to uv for the sake of improving performance and dealing with conflicting sub-dependencies
- refactor: make it ready to be used for local widgets

## Version 0.9.8

Expand Down
60 changes: 26 additions & 34 deletions headless_kivy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Implement a Kivy widget that renders everything to SPI display while being headless.
"""Implement a Kivy widget that renders everything in memory.
* IMPORTANT: You need to run `setup_headless` function before instantiating
`HeadlessWidget`.
A Kivy widget that renders itself on a display connected to the SPI controller of RPi.
It doesn't create any window in any display manager (a.k.a "headless").
A Kivy widget rendered in memory which doesn't create any window in any display manager
(a.k.a "headless").
When no animation is running, you can drop fps to `min_fps` by calling
`activate_low_fps_mode`.
Expand Down Expand Up @@ -50,7 +50,7 @@ def apply_tranformations(data: NDArray[np.uint8]) -> NDArray[np.uint8]:


class HeadlessWidget(Widget):
"""Headless Kivy widget class rendering on SPI connected display."""
"""A Kivy widget that renders everything in memory."""

_is_setup_headless_called: bool = False
should_ignore_hash: bool = False
Expand Down Expand Up @@ -193,8 +193,8 @@ def activate_low_fps_mode(self: HeadlessWidget) -> None:
self.render()
Clock.schedule_once(lambda _: self._activate_low_fps_mode(), 0)

def render_on_display(self: HeadlessWidget, *_: object) -> None:
"""Render the widget on display connected to the SPI controller."""
def render_on_display(self: HeadlessWidget, *_: object) -> None: # noqa: C901
"""Render the current frame on the display."""
# Log the number of skipped and rendered frames in the last second
if config.is_debug_mode():
# Increment rendered_frames/skipped_frames count every frame and reset their
Expand Down Expand Up @@ -225,21 +225,6 @@ def render_on_display(self: HeadlessWidget, *_: object) -> None:
# Considering the content has not changed, this frame can safely be ignored
return

if config.is_debug_mode():
self.rendered_frames += 1
with Path('headless_kivy_buffer.raw').open('wb') as file:
file.write(
bytes(
data.reshape(
config.width(),
config.height(),
-1,
)
.flatten()
.tolist(),
),
)

self.should_ignore_hash = False

self.last_change = time.time()
Expand All @@ -254,25 +239,32 @@ def render_on_display(self: HeadlessWidget, *_: object) -> None:
except Empty:
last_thread = None

height = int(min(self.height, dp(config.height()) - self.y))
width = int(min(self.width, dp(config.width()) - self.x))
height = int(min(self.texture.height, dp(config.height()) - self.y))
width = int(min(self.texture.width, dp(config.width()) - self.x))

data = data.reshape(int(self.height), int(self.width), -1)
data = data.reshape(int(self.texture.height), int(self.texture.width), -1)
data = data[:height, :width, :]
data = apply_tranformations(data)
x, y = int(self.x), int(dp(config.height()) - self.y - self.height)

if config.is_debug_mode():
self.rendered_frames += 1
raw_file_path = Path('headless_kivy_buffer.raw')

if not raw_file_path.exists():
with raw_file_path.open('wb') as file:
file.write(
b'\x00' * int(dp(config.width()) * dp(config.height()) * 4),
)
with raw_file_path.open('r+b') as file:
for i in range(height):
file.seek(int((x + (y + i) * dp(config.width())) * 4))
file.write(bytes(data[i, :, :].flatten().tolist()))

if config.rotation() % 2 == 0:
HeadlessWidget.raw_data[
self.y : self.y + height,
self.x : self.x + width,
:,
] = data
HeadlessWidget.raw_data[y : y + height, x : x + width, :] = data
else:
HeadlessWidget.raw_data[
self.x : self.x + width,
self.y : self.y + height,
:,
] = data
HeadlessWidget.raw_data[x : x + width, y : y + height, :] = data

thread = Thread(
target=config.callback(),
Expand Down

0 comments on commit c2017eb

Please sign in to comment.