Ui¶
ui ¶
UI-layer exports for screens, slots, cards, and controls.
BlankCard ¶
Bases: Card
A card that renders nothing, letting the touchstrip background show through.
Used as the default placeholder in :class:~deux.ui.touch_strip.TouchStrip
before the user assigns real cards. Returns None from :meth:render
so the compositor skips the slot and the
:attr:~deux.ui.touch_strip.TouchStrip.background_color is visible.
Source code in src/deux/ui/cards/blank.py
Card ¶
Bases: ABC
Abstract base for a single touch-strip zone under an encoder.
The touchscreen is divided into zones aligned with the device's
encoders. Cards are tiled edge-to-edge — the library imposes no
margins or gaps. Each zone is touchscreen_width // panel_count
wide and touchscreen_height tall (e.g. 200x100 per zone on
Stream Deck+).
Subclass this to build custom widgets. At minimum, implement
:meth:render. Override the handle_encoder_* and
check_selection_timeout hooks to react to encoder events.
Examples:
::
class MyCard(Card):
def render(self) -> Image.Image:
img = Image.new("RGB", (panel_width, panel_height), "black")
# ... draw custom content ...
return img
Event handlers are registered with decorators::
@card.on_tap
async def handle():
print("Card tapped!")
Source code in src/deux/ui/cards/base.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 | |
has_pending_callbacks
property
¶
Whether the card has callbacks queued for the next drain.
Returns:
| Type | Description |
|---|---|
bool
|
|
on_tap ¶
Decorator to register a handler for short tap events in this zone.
Examples:
::
@widget.on_tap
async def handle():
...
Source code in src/deux/ui/cards/base.py
on_long_press ¶
Decorator to register a handler for long press events in this zone.
Examples:
::
@widget.on_long_press
async def handle():
...
Source code in src/deux/ui/cards/base.py
on_drag ¶
Decorator to register a handler for drag/swipe events in this zone.
The handler receives x, y, x_out, y_out arguments
describing the start and end coordinates of the drag gesture.
Examples:
::
@widget.on_drag
async def handle(x: int, y: int, x_out: int, y_out: int):
...
Source code in src/deux/ui/cards/base.py
on_encoder_turn ¶
Decorator to register a handler for encoder turn events on this widget.
The handler receives a single direction argument:
positive = clockwise, negative = counter-clockwise.
Examples:
::
@widget.on_encoder_turn
async def handle(direction: int):
...
Source code in src/deux/ui/cards/base.py
on_encoder_press ¶
Decorator to register a handler for encoder press events on this widget.
Examples:
::
@widget.on_encoder_press
async def handle():
...
Source code in src/deux/ui/cards/base.py
on_encoder_release ¶
Decorator to register a handler for encoder release events on this widget.
Examples:
::
@widget.on_encoder_release
async def handle():
...
Source code in src/deux/ui/cards/base.py
set_refresh_callback ¶
Register an async callback the card can invoke to request a refresh.
Multiple callbacks may be registered (e.g. when the same card is installed on screens belonging to different decks). Each call adds callback to the list — duplicates are silently ignored so that re-wiring the same deck does not accumulate entries.
This is set automatically by :class:~deux.runtime.deck.Deck
when dispatching events so that cards with internal timers (e.g.
long-press detection) can trigger a re-render without a direct
reference to the deck.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
AsyncHandler
|
Async callable that triggers a deck refresh. |
required |
Source code in src/deux/ui/cards/base.py
remove_refresh_callback ¶
Remove a previously registered refresh callback.
No-op if callback is not in the list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
AsyncHandler
|
The callback to remove. |
required |
Source code in src/deux/ui/cards/base.py
request_refresh
async
¶
Ask all registered decks to re-render this card.
No-op if no refresh callbacks have been registered.
queue_pending_callback ¶
Enqueue a callback for deferred async invocation.
Called by child elements (e.g. sliders) when their value changes
synchronously. The queued callbacks are drained and awaited by
:class:~deux.runtime.deck.Deck during event dispatch or refresh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
AsyncHandler
|
The async callback to invoke. |
required |
args
|
tuple[object, ...]
|
Positional arguments to pass to the callback. |
required |
Source code in src/deux/ui/cards/base.py
drain_pending_callbacks ¶
Remove and return all pending callbacks.
Returns:
| Type | Description |
|---|---|
list[tuple[AsyncHandler, tuple[object, ...]]]
|
A list of |
Source code in src/deux/ui/cards/base.py
mark_clean ¶
mark_dirty ¶
set_rendered ¶
Store the rendered image and clear the dirty flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
img
|
Image | None
|
The rendered PIL image, or |
required |
Source code in src/deux/ui/cards/base.py
prepare_assets
async
¶
render_panel_bytes ¶
render_panel_bytes(*, metrics: RenderMetrics, card_index: int, bg_tile: bytes | None, background: str = 'black', image_format: str = 'JPEG') -> bytes
Render the card to encoded image bytes for a touchscreen panel.
The base implementation uses Pillow compositing: it calls
:meth:render and composites the result onto the background tile.
Subclasses (e.g. :class:~deux.dui.card.DuiCard) override this
to use their native rendering pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics
|
RenderMetrics
|
Device metrics (panel dimensions, etc.). |
required |
card_index
|
int
|
The zero-based position of this card on the touch strip. |
required |
bg_tile
|
bytes or None
|
PNG-encoded background tile for this panel, or |
required |
background
|
str
|
Fallback background colour. |
"black"
|
image_format
|
str
|
Image encoding format ( |
"JPEG"
|
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded image bytes ready to send to the device. |
Source code in src/deux/ui/cards/base.py
dispatch_encoder_turn
async
¶
Dispatch an encoder-turn event to the card.
dispatch_encoder_press
async
¶
Dispatch an encoder-press event to the card.
dispatch_encoder_release
async
¶
Dispatch an encoder-release event to the card.
dispatch_touch
async
¶
Dispatch a touch gesture to the card.
Source code in src/deux/ui/cards/base.py
render
abstractmethod
¶
Render this card as a panel-sized PIL Image.
Return None to let the touchstrip background colour show
through (used by :class:~deux.ui.cards.blank.BlankCard).
Returns:
| Type | Description |
|---|---|
Image or None
|
A panel-sized RGB :class: |
Source code in src/deux/ui/cards/base.py
handle_encoder_turn ¶
Called when the encoder above this widget is turned.
Override to handle encoder rotation. The default is a no-op.
handle_encoder_press ¶
Called when the encoder above this widget is pressed.
Override to handle encoder presses. The default is a no-op.
handle_encoder_release ¶
Called when the encoder above this widget is released.
Override to handle encoder releases. The default is a no-op.
check_selection_timeout ¶
Check whether an internal selection timeout has elapsed.
Override to implement timeout logic (e.g. for slider cycling).
The default always returns False.
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/deux/ui/cards/base.py
CardController ¶
Base class for service-backed touch-strip card controllers.
Subclasses are expected to:
- Load a
.duipackage and assign the resulting :class:~deux.DuiCardto :attr:cardin__init__. - Wire service-event subscriptions and DUI-event forwards (typically
via :meth:
~deux.DuiCard.bind, :meth:~deux.DuiCard.bind_range, :meth:~deux.DuiCard.bind_many, :meth:~deux.DuiCard.forward). - Optionally override :meth:
on_attach/ :meth:on_detachfor deck-linked subscriptions or background task lifecycle.
The base class itself stores no state and performs no wiring. It exists purely to give every controller a uniform lifecycle the application can drive in a loop on connect/disconnect.
Attributes:
| Name | Type | Description |
|---|---|---|
card |
DuiCard
|
The card the controller drives. Subclasses must assign this. |
Source code in src/deux/ui/controller.py
on_attach
async
¶
Hook invoked from the app's on_connect callback.
Default implementation is a no-op. Override to subscribe to
deck-owned events (e.g. deck.on_brightness_changed), replay
last-known values to the freshly-connected hardware, or start
background tasks that depend on a refresh callback being wired
up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deck
|
Deck
|
The freshly-connected :class: |
required |
Source code in src/deux/ui/controller.py
on_detach
async
¶
Hook invoked from the app's on_disconnect callback.
The default implementation is a no-op. Override to cancel background tasks or perform additional teardown.
.. note::
Deck-owned event subscriptions (e.g. on_brightness_changed)
are cleaned up automatically by :meth:Deck.stop via
:meth:~deux.DuiCard.detach_events. Service-owned bindings
established in __init__ are deliberately preserved so that
they survive reconnect cycles without re-wiring.
Source code in src/deux/ui/controller.py
KeyController ¶
Base class for service-backed key controllers.
Mirror of :class:CardController for :class:~deux.DuiKey.
Subclasses construct the key and wire bindings in __init__;
override :meth:on_attach / :meth:on_detach when deck access
or background tasks are required.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
DuiKey
|
The key the controller drives. Subclasses must assign this. |
Source code in src/deux/ui/controller.py
on_attach
async
¶
Hook invoked from the app's on_connect callback.
Default implementation is a no-op. See
:meth:CardController.on_attach for guidance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deck
|
Deck
|
The freshly-connected :class: |
required |
Source code in src/deux/ui/controller.py
on_detach
async
¶
Hook invoked from the app's on_disconnect callback.
The default implementation calls :meth:~deux.DuiKey.detach
to unsubscribe all AsyncEvent handlers. Override to add
additional teardown logic, but call await super().on_detach()
to preserve the unsubscription behaviour.
Source code in src/deux/ui/controller.py
DialAccumulator ¶
Debounce rapid dial/encoder ticks and flush them with a single callback.
callback - async def callback(steps: int) called once per flush
with the net accumulated tick count (signed).
delay - seconds to wait after the last tick before flushing.
max_steps - cap on how many ticks can accumulate (positive number).
Use max_steps=1 to collapse any number of ticks into
a single +1 / -1 event (useful for next/previous).
Examples:
::
acc = DialAccumulator(my_handler, delay=0.2, max_steps=5)
@encoder.on_turn
async def on_turn(direction: int):
acc.tick(direction)
Source code in src/deux/ui/controls/dial_accumulator.py
tick ¶
Add direction (+1 or -1). Clamps to ±max_steps.
Cancels and awaits any previously scheduled flush task before creating a new one, preventing orphaned task accumulation.
Source code in src/deux/ui/controls/dial_accumulator.py
cancel
async
¶
Cancel any pending flush and reset the accumulated count.
Awaits the task's cancellation to ensure the flush handler has fully stopped before returning.
Source code in src/deux/ui/controls/dial_accumulator.py
EncoderSlot ¶
Represents a single physical rotary encoder on the Stream Deck+.
Examples:
Use decorators to register event handlers::
@encoder.on_turn
async def handle(direction: int):
print(f"Turned by {direction}")
Source code in src/deux/ui/controls/encoder_slot.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
on_turn ¶
Decorator to register a handler for encoder turn events.
The handler receives a single direction argument:
positive = clockwise, negative = counter-clockwise.
When a press_turn handler is also registered, on_turn only
fires for turns while the encoder is not pressed.
Examples:
::
@encoder.on_turn
async def handle(direction: int):
...
Source code in src/deux/ui/controls/encoder_slot.py
on_turn_accumulated ¶
on_turn_accumulated(callback: Callable[[int], Awaitable[None]] | None = None, *, delay: float = 0.25, max_steps: int = 10) -> DialAccumulator | Callable[[Callable[[int], Awaitable[None]]], DialAccumulator]
Register an accumulated turn handler backed by a :class:DialAccumulator.
Can be used as a plain decorator or called with keyword arguments::
# Plain decorator — default delay/max_steps
@encoder.on_turn_accumulated
async def handle(steps: int):
...
# With options
@encoder.on_turn_accumulated(delay=0.1, max_steps=5)
async def handle(steps: int):
...
Returns the :class:DialAccumulator instance (which replaces the
decorated function reference).
Source code in src/deux/ui/controls/encoder_slot.py
on_press_turn ¶
Decorator to register a handler for turning while pressed.
The handler receives a single direction argument, just like
:meth:on_turn. When registered, on_turn will not fire
for turns that happen while the encoder is held down.
Examples:
::
@encoder.on_press_turn
async def handle(direction: int):
...
Source code in src/deux/ui/controls/encoder_slot.py
on_press_turn_accumulated ¶
on_press_turn_accumulated(callback: Callable[[int], Awaitable[None]] | None = None, *, delay: float = 0.25, max_steps: int = 10) -> DialAccumulator | Callable[[Callable[[int], Awaitable[None]]], DialAccumulator]
Register an accumulated press-turn handler backed by a :class:DialAccumulator.
Same API as :meth:on_turn_accumulated but only fires while the
encoder is held down.
Examples:
::
@encoder.on_press_turn_accumulated(delay=0.1, max_steps=5)
async def handle(steps: int):
...
Source code in src/deux/ui/controls/encoder_slot.py
on_press ¶
Decorator to register a handler for encoder press events.
Examples:
::
@encoder.on_press
async def handle():
...
Source code in src/deux/ui/controls/encoder_slot.py
on_release ¶
Decorator to register a handler for encoder release events.
Examples:
::
@encoder.on_release
async def handle():
...
Source code in src/deux/ui/controls/encoder_slot.py
dispatch_turn
async
¶
Dispatch an encoder turn event through the registered handler.
When a press_turn handler is registered, turns while pressed
are routed to it instead of the regular turn handler (matching
the priority logic of :class:~deux.dui.event_map.EventMap).
Source code in src/deux/ui/controls/encoder_slot.py
dispatch_press
async
¶
Dispatch an encoder press or release event.
Source code in src/deux/ui/controls/encoder_slot.py
KeySlot ¶
Represents a single physical key on the Stream Deck.
Examples:
Use decorators to register event handlers::
@key.on_press
async def handle():
print("pressed!")
Source code in src/deux/ui/controls/key_slot.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
image_bytes
property
¶
The pre-rendered image bytes, or None if not yet rendered.
set_refresh_callback ¶
Register an async callback the key can invoke to request a refresh.
Multiple callbacks may be registered (e.g. when the same key is installed on screens belonging to different decks). Each call adds callback to the list — duplicates are silently ignored so that re-wiring the same deck does not accumulate entries.
This is set automatically by :class:~deux.runtime.deck.Deck
when a screen is activated, so any code path (key handler,
background task, external state change) can call
:meth:request_refresh to trigger a re-render.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
AsyncHandler
|
Async callable that triggers a deck refresh. |
required |
Source code in src/deux/ui/controls/key_slot.py
remove_refresh_callback ¶
Remove a previously registered refresh callback.
No-op if callback is not in the list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
AsyncHandler
|
The callback to remove. |
required |
Source code in src/deux/ui/controls/key_slot.py
request_refresh
async
¶
Ask all registered decks to re-render the active screen.
No-op if no refresh callbacks have been registered.
on_press ¶
Decorator to register a handler for key press events.
Examples:
::
@key.on_press
async def handle():
...
Source code in src/deux/ui/controls/key_slot.py
on_release ¶
Decorator to register a handler for key release events.
Examples:
::
@key.on_release
async def handle():
...
Source code in src/deux/ui/controls/key_slot.py
dispatch
async
¶
Dispatch a press or release event through the internal hook.
Subclasses should override :meth:_dispatch_event to customise
event handling without replacing the public entry point.
Source code in src/deux/ui/controls/key_slot.py
mark_dirty ¶
mark_clean ¶
set_rendered_image ¶
InfoScreen ¶
Manage content on a non-touch info screen (e.g. Stream Deck Neo 248x58).
The info screen is a small display that shows status information. It does not support touch events or interactive cards — it is a simple image buffer with dirty tracking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Screen width in pixels. |
required |
height
|
int
|
Screen height in pixels. |
required |
image_format
|
str
|
Image format for encoding ( |
'JPEG'
|
Source code in src/deux/ui/info_screen.py
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
set_image ¶
Set the info screen image and mark dirty.
The image is resized to fit the screen dimensions if needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Image
|
A PIL Image to display. |
required |
Source code in src/deux/ui/info_screen.py
clear ¶
mark_clean ¶
mark_dirty ¶
render_bytes ¶
Encode the current image to bytes in the device's format.
Returns:
| Type | Description |
|---|---|
bytes
|
Encoded image bytes. If no image has been set, returns a blank black image. |
Source code in src/deux/ui/info_screen.py
Screen ¶
A named layout containing keys, encoders, and touch-strip cards.
Screens allow you to define multiple layouts and switch between them. When a screen is activated, all key images, touch-strip cards, and event handlers swap atomically.
The number of available keys, encoders, and card zones is determined
by the device capabilities. For devices without encoders or a
touchscreen, those features are unavailable and accessing them raises
:class:~deux.runtime.deck.DeckError.
Examples:
::
main = deck.screen("main")
@main.key(0).on_press
async def handle():
await deck.set_screen("settings")
Source code in src/deux/ui/screen.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 | |
key_bg_image
property
¶
Pre-rendered default key background image, or None.
Returns:
| Type | Description |
|---|---|
bytes or None
|
Encoded image bytes ready to push to the device, or |
key_bg_dirty
property
¶
Whether the key background needs re-rendering on all blank keys.
capabilities
property
¶
The device capabilities this screen is configured for.
theme
property
writable
¶
Per-screen theme override, or None to inherit.
When set, this theme takes precedence over both the deck-level
and system-wide theme for this screen. Set to None to fall
back to the deck or system theme.
keys
property
¶
Mapping of key index to :class:KeySlot for all configured keys.
Returns a read-only view; external code cannot mutate internal state.
encoders
property
¶
Mapping of encoder index to :class:EncoderSlot for all configured encoders.
Returns a read-only view; external code cannot mutate internal state.
touch_strip
property
¶
The touch strip, or None if the device has no touchscreen.
info_screen
property
¶
The info screen, or None if the device has no info display.
touchstrip_background
property
writable
¶
The fill colour for the touchscreen canvas behind cards.
cards
property
¶
All touch-strip cards, or an empty list if the device has no touchscreen.
Returns a shallow copy; external code cannot mutate internal state.
__init__ ¶
Initialise a screen for the given device capabilities.
Screens are normally created via :meth:Deck.screen rather than
instantiated directly; the deck supplies the matching device
capabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Identifier used to look up and activate this screen via
:meth: |
required |
caps
|
DeviceCapabilities
|
Capability snapshot describing the target device. Drives which controls are provisioned (touch strip, info screen) and which slot indices are valid. |
required |
Notes
Construction has the following side effects:
- A :class:
~deux.render.background_layer.BackgroundLayeris created for keys regardless of device. - A :class:
~deux.ui.touch_strip.TouchStripis provisioned only when the device has both a touchscreen and at least one dial. - An :class:
~deux.ui.info_screen.InfoScreenis provisioned only when the device reports an info screen. - Bundled default backgrounds for the device's VID:PID are applied on a best-effort basis; failures are logged but never raised.
Source code in src/deux/ui/screen.py
clear_key_bg_dirty ¶
key ¶
Get or create a key slot by index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Key index (0 to key_count-1). |
required |
Returns:
| Type | Description |
|---|---|
KeySlot
|
The KeySlot instance for this key on this screen. |
Source code in src/deux/ui/screen.py
set_key ¶
Replace the key slot at index with a custom key.
The same KeySlot (or :class:~deux.dui.DuiKey) instance may
be installed on multiple screens at different slot indices — the
screen's slot map is the single source of truth for routing, so
no state is mutated on key itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Key index. |
required |
key
|
KeySlot
|
The key slot to install. |
required |
Source code in src/deux/ui/screen.py
encoder ¶
Get or create an encoder slot by index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Encoder index (0 to dial_count-1). |
required |
Returns:
| Type | Description |
|---|---|
EncoderSlot
|
The EncoderSlot instance for this encoder on this screen. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If index is out of range or device has no encoders. |
Source code in src/deux/ui/screen.py
card ¶
set_card ¶
Replace the card at index with a custom card.
set_touchstrip_background_svg ¶
Set a background SVG for the touchstrip.
The SVG is rasterized once, sliced into per-panel tiles, and
cached. Cards with transparent areas will show the background
through. When no background SVG is set, the solid
:attr:touchstrip_background colour is used instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
svg_data
|
bytes
|
Raw SVG content as UTF-8 bytes. |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If the device has no touchscreen. |
Source code in src/deux/ui/screen.py
set_touchstrip_background_svg_from_file ¶
Load a touchstrip background SVG from a file path.
Convenience wrapper around :meth:set_touchstrip_background_svg.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to an SVG file. |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If the device has no touchscreen. |
FileNotFoundError
|
If path does not exist. |
Source code in src/deux/ui/screen.py
clear_touchstrip_background_svg ¶
Remove the touchstrip background SVG.
Reverts to the solid :attr:touchstrip_background colour.
Raises:
| Type | Description |
|---|---|
IndexError
|
If the device has no touchscreen. |
Source code in src/deux/ui/screen.py
mark_all_dirty ¶
Flag every control on this screen for re-rendering.
Marks all configured keys, touch-strip cards, and the info screen (if present) as dirty so the next refresh cycle re-renders the entire screen. If the touch strip has a background SVG it is re-rasterized so that stylesheet changes are reflected. Useful when a global property such as the active stylesheet changes.
Source code in src/deux/ui/screen.py
collect_all_icons ¶
Collect all Iconify icon identifiers needed by this screen.
Iterates all DuiKey and DuiCard instances on this screen and
aggregates their :meth:~deux.dui.svg_renderer.SvgRenderer.collect_icon_names
results.
Returns:
| Type | Description |
|---|---|
set[str]
|
A set of |
Source code in src/deux/ui/screen.py
screenshot ¶
Save the current screen state as individual PNG files.
Writes one file per key that has been rendered, one file per touch-strip card that has been rendered, and one file for the info screen (if it has content). Blank or unrendered controls are skipped.
PNG is used instead of JPEG to avoid additional compression artifacts on the already-small device images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
str or Path
|
Target directory for the screenshot files. Created (including parents) if it does not already exist. |
required |
Returns:
| Type | Description |
|---|---|
list[Path]
|
Paths of all files written, in the order they were saved. |
Examples:
::
paths = screen.screenshot("/tmp/deck_screenshot")
# [PosixPath('/tmp/deck_screenshot/key_0.png'),
# PosixPath('/tmp/deck_screenshot/card_1.png')]
Source code in src/deux/ui/screen.py
TouchStrip ¶
Manage card zones on the Stream Deck touch strip.
The number of card zones is determined by the device's dial count (typically 4 for Stream Deck+).
The background_color fills the entire touchscreen canvas — including
the margin and gap areas outside card panels. Each :class:Screen
owns its own TouchStrip, so different screens can use different
background colours.
An optional background SVG (covering the full touchscreen, e.g.
800x100 on Stream Deck+) can be set via :meth:set_background_svg.
When set, the SVG is rasterized once, sliced into per-panel tiles,
and cached. Cards render with transparent backgrounds and are
composited onto their tile at render time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
panel_count
|
int
|
Number of card zones. |
4
|
panel_width
|
int
|
Width of each card panel in pixels. |
200
|
panel_height
|
int
|
Height of each card panel in pixels. |
100
|
background_color
|
str
|
Initial background colour. |
'black'
|
Source code in src/deux/ui/touch_strip.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | |
background_color
property
writable
¶
The fill colour for the touchscreen canvas (margins and gaps).
cards
property
¶
All card zones on this touch strip.
Returns a shallow copy; external code cannot mutate internal state.
bg_tiles
property
¶
Pre-sliced background tiles as PNG bytes, or None if no background SVG is set.
Returns a shallow copy when tiles exist; external code cannot mutate internal state.
bg_svg_root
property
¶
The parsed background SVG root element, or None.
Used by the SVG-native pipeline to compose background layers with card SVGs at the vector level before rasterisation.
bg_layer
property
¶
The background layer managing SVG, tiles, and rasterisation.
card ¶
Get a card zone by index.
set_card ¶
Replace the card at index with a custom card.
The same Card instance may be installed on multiple screens
(and, on a single screen, in different slots across screens) —
the strip's slot list is the single source of truth for routing,
so no state is mutated on card itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Card zone index. |
required |
card
|
Card
|
A :class: |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If index is out of range. |
TypeError
|
If card is not a :class: |
Source code in src/deux/ui/touch_strip.py
bg_tile ¶
Return the cached background tile for panel index, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Panel index (0 to panel_count-1). |
required |
Returns:
| Type | Description |
|---|---|
bytes or None
|
PNG-encoded tile bytes, or |
Source code in src/deux/ui/touch_strip.py
set_background_svg ¶
Set a background SVG for the entire touchstrip.
The SVG is parsed and cached as an XML element tree for SVG-level composition. For backward compatibility, the SVG is also rasterized and sliced into per-panel PIL tiles.
All cards are marked dirty so they re-render with the new background.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
svg_data
|
bytes
|
Raw SVG content as UTF-8 bytes. |
required |
Source code in src/deux/ui/touch_strip.py
set_background_svg_from_file ¶
Load a background SVG from a file path.
Convenience wrapper around :meth:set_background_svg.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to an SVG file. |
required |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If path does not exist. |
Source code in src/deux/ui/touch_strip.py
clear_background_svg ¶
Remove the background SVG and revert to solid-colour background.
All cards are marked dirty so they re-render without the background tiles.
Source code in src/deux/ui/touch_strip.py
invalidate_background ¶
Re-rasterize the cached background SVG tiles.
Call this when a global property that affects SVG rendering (such as the active stylesheet) has changed. If no background SVG is set this is a no-op.
Source code in src/deux/ui/touch_strip.py
set_background_svg_async
async
¶
Async variant of :meth:set_background_svg.
Offloads the CPU-bound SVG rasterisation to a worker thread so the event loop stays responsive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
svg_data
|
bytes
|
Raw SVG content as UTF-8 bytes. |
required |
Source code in src/deux/ui/touch_strip.py
invalidate_background_async
async
¶
Async variant of :meth:invalidate_background.
Offloads the CPU-bound SVG rasterisation to a worker thread so the event loop stays responsive.