1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 14:58:09 +00:00

refactor(core): limit global imports in key places

so that by importing `apps.base`, we don't pull in the whole circus
This commit is contained in:
matejcik 2021-03-22 16:14:24 +01:00 committed by matejcik
parent ae0da5e245
commit e859c13d70
12 changed files with 110 additions and 65 deletions

View File

@ -1,22 +1,15 @@
import storage import storage.cache
import storage.device import storage.device
import storage.recovery from trezor import config, utils, wire, workflow
import storage.sd_salt from trezor.messages import MessageType
from storage import cache
from trezor import config, sdcard, ui, utils, wire, workflow
from trezor.messages import Capability, MessageType
from trezor.messages.Features import Features
from trezor.messages.PreauthorizedRequest import PreauthorizedRequest
from trezor.messages.Success import Success from trezor.messages.Success import Success
from apps.common import mnemonic, safety_checks
from apps.common.request_pin import verify_user_pin
from . import workflow_handlers from . import workflow_handlers
if False: if False:
import protobuf import protobuf
from typing import Iterable, NoReturn, Protocol from typing import Iterable, NoReturn, Protocol
from trezor.messages.Features import Features
from trezor.messages.Initialize import Initialize from trezor.messages.Initialize import Initialize
from trezor.messages.EndSession import EndSession from trezor.messages.EndSession import EndSession
from trezor.messages.GetFeatures import GetFeatures from trezor.messages.GetFeatures import GetFeatures
@ -37,6 +30,15 @@ if False:
def get_features() -> Features: def get_features() -> Features:
import storage.recovery
import storage.sd_salt
from trezor import sdcard
from trezor.messages import Capability
from trezor.messages.Features import Features
from apps.common import mnemonic, safety_checks
f = Features( f = Features(
vendor="trezor.io", vendor="trezor.io",
language="en-US", language="en-US",
@ -107,7 +109,7 @@ async def handle_Initialize(ctx: wire.Context, msg: Initialize) -> Features:
features = get_features() features = get_features()
if msg.session_id: if msg.session_id:
msg.session_id = bytes(msg.session_id) msg.session_id = bytes(msg.session_id)
features.session_id = cache.start_session(msg.session_id) features.session_id = storage.cache.start_session(msg.session_id)
return features return features
@ -125,7 +127,7 @@ async def handle_LockDevice(ctx: wire.Context, msg: LockDevice) -> Success:
async def handle_EndSession(ctx: wire.Context, msg: EndSession) -> Success: async def handle_EndSession(ctx: wire.Context, msg: EndSession) -> Success:
cache.end_current_session() storage.cache.end_current_session()
return Success() return Success()
@ -141,6 +143,8 @@ async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success:
async def handle_DoPreauthorized( async def handle_DoPreauthorized(
ctx: wire.Context, msg: DoPreauthorized ctx: wire.Context, msg: DoPreauthorized
) -> protobuf.MessageType: ) -> protobuf.MessageType:
from trezor.messages.PreauthorizedRequest import PreauthorizedRequest
authorization: Authorization = storage.cache.get( authorization: Authorization = storage.cache.get(
storage.cache.APP_BASE_AUTHORIZATION storage.cache.APP_BASE_AUTHORIZATION
) )
@ -194,6 +198,8 @@ ALLOW_WHILE_LOCKED = (
def set_homescreen() -> None: def set_homescreen() -> None:
import storage.recovery
if not config.is_unlocked(): if not config.is_unlocked():
from apps.homescreen.lockscreen import lockscreen from apps.homescreen.lockscreen import lockscreen
@ -229,6 +235,8 @@ async def unlock_device(ctx: wire.GenericContext = wire.DUMMY_CONTEXT) -> None:
If the storage is locked, attempt to unlock it. Reset the homescreen and the wire If the storage is locked, attempt to unlock it. Reset the homescreen and the wire
handler. handler.
""" """
from apps.common.request_pin import verify_user_pin
if not config.is_unlocked(): if not config.is_unlocked():
# verify_user_pin will raise if the PIN was invalid # verify_user_pin will raise if the PIN was invalid
await verify_user_pin(ctx) await verify_user_pin(ctx)
@ -264,6 +272,8 @@ def get_pinlocked_handler(
# this function is also called when handling ApplySettings # this function is also called when handling ApplySettings
def reload_settings_from_storage() -> None: def reload_settings_from_storage() -> None:
from trezor import ui
workflow.idle_timer.set( workflow.idle_timer.set(
storage.device.get_autolock_delay_ms(), lock_device_if_unlocked storage.device.get_autolock_delay_ms(), lock_device_if_unlocked
) )

View File

@ -1,8 +1,6 @@
import storage.device import storage.device
from trezor import ui, utils, workflow from trezor import ui, utils, workflow
from trezor.crypto import bip39, slip39
from trezor.messages import BackupType from trezor.messages import BackupType
from trezor.ui.components.tt.text import Text
if False: if False:
from trezor.messages.ResetDevice import EnumTypeBackupType from trezor.messages.ResetDevice import EnumTypeBackupType
@ -39,9 +37,13 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
render_func = _render_progress render_func = _render_progress
if is_bip39(): if is_bip39():
from trezor.crypto import bip39
seed = bip39.seed(mnemonic_secret.decode(), passphrase, render_func) seed = bip39.seed(mnemonic_secret.decode(), passphrase, render_func)
else: # SLIP-39 else: # SLIP-39
from trezor.crypto import slip39
identifier = storage.device.get_slip39_identifier() identifier = storage.device.get_slip39_identifier()
iteration_exponent = storage.device.get_slip39_iteration_exponent() iteration_exponent = storage.device.get_slip39_iteration_exponent()
if identifier is None or iteration_exponent is None: if identifier is None or iteration_exponent is None:
@ -55,6 +57,8 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
def _start_progress() -> None: def _start_progress() -> None:
from trezor.ui.components.tt.text import Text
# Because we are drawing to the screen manually, without a layout, we # Because we are drawing to the screen manually, without a layout, we
# should make sure that no other layout is running. # should make sure that no other layout is running.
workflow.close_others() workflow.close_others()

View File

@ -3,10 +3,7 @@ from micropython import const
import storage.device import storage.device
from trezor import wire, workflow from trezor import wire, workflow
from trezor.messages import ButtonRequestType from trezor.messages import ButtonRequestType
from trezor.messages.PassphraseAck import PassphraseAck
from trezor.messages.PassphraseRequest import PassphraseRequest
from trezor.ui import ICON_CONFIG, draw_simple from trezor.ui import ICON_CONFIG, draw_simple
from trezor.ui.components.tt.passphrase import CANCELLED, PassphraseKeyboard
from trezor.ui.components.tt.text import Text from trezor.ui.components.tt.text import Text
from . import button_request from . import button_request
@ -41,6 +38,9 @@ async def _request_from_user(ctx: wire.Context) -> str:
async def _request_on_host(ctx: wire.Context) -> str: async def _request_on_host(ctx: wire.Context) -> str:
from trezor.messages.PassphraseAck import PassphraseAck
from trezor.messages.PassphraseRequest import PassphraseRequest
_entry_dialog() _entry_dialog()
request = PassphraseRequest() request = PassphraseRequest()
@ -74,6 +74,8 @@ async def _request_on_host(ctx: wire.Context) -> str:
async def _request_on_device(ctx: wire.Context) -> str: async def _request_on_device(ctx: wire.Context) -> str:
from trezor.ui.components.tt.passphrase import CANCELLED, PassphraseKeyboard
await button_request(ctx, code=ButtonRequestType.PassphraseEntry) await button_request(ctx, code=ButtonRequestType.PassphraseEntry)
keyboard = PassphraseKeyboard("Enter passphrase", _MAX_PASSPHRASE_LEN) keyboard = PassphraseKeyboard("Enter passphrase", _MAX_PASSPHRASE_LEN)

View File

@ -4,17 +4,19 @@ if not __debug__:
halt("debug mode inactive") halt("debug mode inactive")
if __debug__: if __debug__:
from trezor import io, ui, wire from trezor import config, log, loop, utils, wire
from trezor.messages import MessageType, DebugSwipeDirection from trezor.ui import display
from trezor.messages import MessageType
from trezor.messages.DebugLinkLayout import DebugLinkLayout from trezor.messages.DebugLinkLayout import DebugLinkLayout
from trezor import config, crypto, log, loop, utils
from trezor.messages.Success import Success from trezor.messages.Success import Success
from apps import workflow_handlers from apps import workflow_handlers
if False: if False:
from trezor.ui import Layout
from trezor.messages.DebugLinkDecision import DebugLinkDecision from trezor.messages.DebugLinkDecision import DebugLinkDecision
from trezor.messages.DebugLinkGetState import DebugLinkGetState from trezor.messages.DebugLinkGetState import DebugLinkGetState
from trezor.messages.DebugLinkLayout import DebugLinkLayout
from trezor.messages.DebugLinkRecordScreen import DebugLinkRecordScreen from trezor.messages.DebugLinkRecordScreen import DebugLinkRecordScreen
from trezor.messages.DebugLinkReseedRandom import DebugLinkReseedRandom from trezor.messages.DebugLinkReseedRandom import DebugLinkReseedRandom
from trezor.messages.DebugLinkState import DebugLinkState from trezor.messages.DebugLinkState import DebugLinkState
@ -43,36 +45,41 @@ if __debug__:
def screenshot() -> bool: def screenshot() -> bool:
if save_screen: if save_screen:
ui.display.save(save_screen_directory + "/refresh-") display.save(save_screen_directory + "/refresh-")
return True return True
return False return False
def notify_layout_change(layout: ui.Layout) -> None: def notify_layout_change(layout: Layout) -> None:
global current_content global current_content
current_content = layout.read_content() current_content = layout.read_content()
if watch_layout_changes: if watch_layout_changes:
layout_change_chan.publish(current_content) layout_change_chan.publish(current_content)
async def debuglink_decision_dispatcher() -> None: async def dispatch_debuglink_decision(msg: DebugLinkDecision) -> None:
from trezor.messages import DebugSwipeDirection
from trezor.ui import Result
from trezor.ui.components.tt import confirm, swipe from trezor.ui.components.tt import confirm, swipe
if msg.yes_no is not None:
await confirm_chan.put(
Result(confirm.CONFIRMED if msg.yes_no else confirm.CANCELLED)
)
if msg.swipe is not None:
if msg.swipe == DebugSwipeDirection.UP:
await swipe_chan.put(swipe.SWIPE_UP)
elif msg.swipe == DebugSwipeDirection.DOWN:
await swipe_chan.put(swipe.SWIPE_DOWN)
elif msg.swipe == DebugSwipeDirection.LEFT:
await swipe_chan.put(swipe.SWIPE_LEFT)
elif msg.swipe == DebugSwipeDirection.RIGHT:
await swipe_chan.put(swipe.SWIPE_RIGHT)
if msg.input is not None:
await input_chan.put(Result(msg.input))
async def debuglink_decision_dispatcher() -> None:
while True: while True:
msg = await debuglink_decision_chan.take() msg = await debuglink_decision_chan.take()
if msg.yes_no is not None: await dispatch_debuglink_decision(msg)
await confirm_chan.put(
ui.Result(confirm.CONFIRMED if msg.yes_no else confirm.CANCELLED)
)
if msg.swipe is not None:
if msg.swipe == DebugSwipeDirection.UP:
await swipe_chan.put(swipe.SWIPE_UP)
elif msg.swipe == DebugSwipeDirection.DOWN:
await swipe_chan.put(swipe.SWIPE_DOWN)
elif msg.swipe == DebugSwipeDirection.LEFT:
await swipe_chan.put(swipe.SWIPE_LEFT)
elif msg.swipe == DebugSwipeDirection.RIGHT:
await swipe_chan.put(swipe.SWIPE_RIGHT)
if msg.input is not None:
await input_chan.put(ui.Result(msg.input))
loop.schedule(debuglink_decision_dispatcher()) loop.schedule(debuglink_decision_dispatcher())
@ -81,6 +88,8 @@ if __debug__:
await ctx.write(DebugLinkLayout(lines=content)) await ctx.write(DebugLinkLayout(lines=content))
async def touch_hold(x: int, y: int, duration_ms: int) -> None: async def touch_hold(x: int, y: int, duration_ms: int) -> None:
from trezor import io
await loop.sleep(duration_ms) await loop.sleep(duration_ms)
loop.synthetic_events.append((io.TOUCH, (io.TOUCH_END, x, y))) loop.synthetic_events.append((io.TOUCH, (io.TOUCH_END, x, y)))
@ -96,6 +105,8 @@ if __debug__:
async def dispatch_DebugLinkDecision( async def dispatch_DebugLinkDecision(
ctx: wire.Context, msg: DebugLinkDecision ctx: wire.Context, msg: DebugLinkDecision
) -> None: ) -> None:
from trezor import io
if debuglink_decision_chan.putters: if debuglink_decision_chan.putters:
log.warning(__name__, "DebugLinkDecision queue is not empty") log.warning(__name__, "DebugLinkDecision queue is not empty")
@ -149,7 +160,7 @@ if __debug__:
save_screen = True save_screen = True
else: else:
save_screen = False save_screen = False
ui.display.clear_save() # clear C buffers display.clear_save() # clear C buffers
return Success() return Success()
@ -157,12 +168,16 @@ if __debug__:
ctx: wire.Context, msg: DebugLinkReseedRandom ctx: wire.Context, msg: DebugLinkReseedRandom
) -> Success: ) -> Success:
if msg.value is not None: if msg.value is not None:
crypto.random.reseed(msg.value) from trezor.crypto import random
random.reseed(msg.value)
return Success() return Success()
async def dispatch_DebugLinkEraseSdCard( async def dispatch_DebugLinkEraseSdCard(
ctx: wire.Context, msg: DebugLinkEraseSdCard ctx: wire.Context, msg: DebugLinkEraseSdCard
) -> Success: ) -> Success:
from trezor import io
try: try:
io.sdcard.power_on() io.sdcard.power_on()
if msg.format: if msg.format:

View File

@ -1,7 +1,7 @@
import gc import gc
from trezorcrypto import random # avoid pulling in trezor.crypto
from trezor import utils from trezor import utils
from trezor.crypto import random
if False: if False:
from typing import Sequence from typing import Sequence

View File

@ -2,8 +2,6 @@ from micropython import const
from ubinascii import hexlify from ubinascii import hexlify
from storage import common from storage import common
from trezor.crypto import random
from trezor.messages import BackupType
if False: if False:
from trezor.messages.ResetDevice import EnumTypeBackupType from trezor.messages.ResetDevice import EnumTypeBackupType
@ -37,8 +35,6 @@ INITIALIZED = const(0x13) # bool (0x01 or empty)
_SAFETY_CHECK_LEVEL = const(0x14) # int _SAFETY_CHECK_LEVEL = const(0x14) # int
_EXPERIMENTAL_FEATURES = const(0x15) # bool (0x01 or empty) _EXPERIMENTAL_FEATURES = const(0x15) # bool (0x01 or empty)
_DEFAULT_BACKUP_TYPE = BackupType.Bip39
SAFETY_CHECK_LEVEL_STRICT : Literal[0] = const(0) SAFETY_CHECK_LEVEL_STRICT : Literal[0] = const(0)
SAFETY_CHECK_LEVEL_PROMPT : Literal[1] = const(1) SAFETY_CHECK_LEVEL_PROMPT : Literal[1] = const(1)
_DEFAULT_SAFETY_CHECK_LEVEL = SAFETY_CHECK_LEVEL_STRICT _DEFAULT_SAFETY_CHECK_LEVEL = SAFETY_CHECK_LEVEL_STRICT
@ -79,6 +75,8 @@ def is_initialized() -> bool:
def _new_device_id() -> str: def _new_device_id() -> str:
from trezorcrypto import random # avoid pulling in trezor.crypto
return hexlify(random.bytes(12)).decode().upper() return hexlify(random.bytes(12)).decode().upper()
@ -121,9 +119,11 @@ def get_mnemonic_secret() -> bytes | None:
def get_backup_type() -> EnumTypeBackupType: def get_backup_type() -> EnumTypeBackupType:
from trezor.messages import BackupType
backup_type = common.get_uint8(_NAMESPACE, _BACKUP_TYPE) backup_type = common.get_uint8(_NAMESPACE, _BACKUP_TYPE)
if backup_type is None: if backup_type is None:
backup_type = _DEFAULT_BACKUP_TYPE backup_type = BackupType.Bip39
if backup_type not in ( if backup_type not in (
BackupType.Bip39, BackupType.Bip39,

View File

@ -1,7 +1,6 @@
from micropython import const from micropython import const
from storage import common, recovery_shares from storage import common
from trezor.crypto import slip39
# Namespace: # Namespace:
_NAMESPACE = common.APP_RECOVERY _NAMESPACE = common.APP_RECOVERY
@ -86,22 +85,26 @@ def set_slip39_remaining_shares(shares_remaining: int, group_index: int) -> None
0x10 (16) was chosen as the default value because it's the max 0x10 (16) was chosen as the default value because it's the max
share count for a group. share count for a group.
""" """
from trezor.crypto.slip39 import MAX_SHARE_COUNT
_require_progress() _require_progress()
remaining = common.get(_NAMESPACE, _REMAINING) remaining = common.get(_NAMESPACE, _REMAINING)
group_count = get_slip39_group_count() group_count = get_slip39_group_count()
if not group_count: if not group_count:
raise RuntimeError raise RuntimeError
if remaining is None: if remaining is None:
remaining = bytearray([slip39.MAX_SHARE_COUNT] * group_count) remaining = bytearray([MAX_SHARE_COUNT] * group_count)
remaining = bytearray(remaining) remaining = bytearray(remaining)
remaining[group_index] = shares_remaining remaining[group_index] = shares_remaining
common.set(_NAMESPACE, _REMAINING, remaining) common.set(_NAMESPACE, _REMAINING, remaining)
def get_slip39_remaining_shares(group_index: int) -> int | None: def get_slip39_remaining_shares(group_index: int) -> int | None:
from trezor.crypto.slip39 import MAX_SHARE_COUNT
_require_progress() _require_progress()
remaining = common.get(_NAMESPACE, _REMAINING) remaining = common.get(_NAMESPACE, _REMAINING)
if remaining is None or remaining[group_index] == slip39.MAX_SHARE_COUNT: if remaining is None or remaining[group_index] == MAX_SHARE_COUNT:
return None return None
else: else:
return remaining[group_index] return remaining[group_index]
@ -120,6 +123,8 @@ def fetch_slip39_remaining_shares() -> list[int] | None:
def end_progress() -> None: def end_progress() -> None:
from . import recovery_shares
_require_progress() _require_progress()
common.delete(_NAMESPACE, _IN_PROGRESS) common.delete(_NAMESPACE, _IN_PROGRESS)
common.delete(_NAMESPACE, _DRY_RUN) common.delete(_NAMESPACE, _DRY_RUN)

View File

@ -1,4 +1,4 @@
from trezor import ui, utils from trezor import utils
if False: if False:
from typing import Any from typing import Any
@ -10,6 +10,8 @@ keepalive_callback: Any = None
def show_pin_timeout(seconds: int, progress: int, message: str) -> bool: def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
from trezor import ui
global _previous_progress global _previous_progress
global _previous_seconds global _previous_seconds

View File

@ -5,9 +5,6 @@ from trezorui import Display
from trezor import io, loop, res, utils, workflow from trezor import io, loop, res, utils, workflow
if __debug__:
from apps.debug import notify_layout_change
if False: if False:
from typing import Any, Awaitable, Generator, TypeVar from typing import Any, Awaitable, Generator, TypeVar
@ -37,9 +34,10 @@ _alert_in_progress = False
# in debug mode, display an indicator in top right corner # in debug mode, display an indicator in top right corner
if __debug__: if __debug__:
from apps.debug import screenshot
def refresh() -> None: def refresh() -> None:
from apps.debug import screenshot
if not screenshot(): if not screenshot():
display.bar(Display.WIDTH - 8, 0, 8, 8, 0xF800) display.bar(Display.WIDTH - 8, 0, 8, 8, 0xF800)
display.refresh() display.refresh()
@ -376,6 +374,8 @@ class Layout(Component):
self.dispatch(RENDER, 0, 0) self.dispatch(RENDER, 0, 0)
if __debug__ and self.should_notify_layout_change: if __debug__ and self.should_notify_layout_change:
from apps.debug import notify_layout_change
# notify about change and do not notify again until next await. # notify about change and do not notify again until next await.
# (handle_rendering might be called multiple times in a single await, # (handle_rendering might be called multiple times in a single await,
# because of the endless loop in __iter__) # because of the endless loop in __iter__)

View File

@ -1,8 +1,5 @@
from trezor import loop, ui, wire from trezor import loop, ui, wire
if __debug__:
from apps.debug import confirm_signal
if False: if False:
from typing import Any, Awaitable from typing import Any, Awaitable
@ -52,4 +49,6 @@ class ConfirmBase(ui.Layout):
return self.content.read_content() return self.content.read_content()
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:
from apps.debug import confirm_signal
return super().create_tasks() + (confirm_signal(),) return super().create_tasks() + (confirm_signal(),)

View File

@ -6,9 +6,6 @@ from trezor.ui.loader import Loader, LoaderDefault
from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase
from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault
if __debug__:
from apps.debug import swipe_signal, confirm_signal
if False: if False:
from typing import Any from typing import Any
from .button import ButtonContent, ButtonStyleType from .button import ButtonContent, ButtonStyleType
@ -96,6 +93,8 @@ class ConfirmPageable(Confirm):
directions = SWIPE_HORIZONTAL directions = SWIPE_HORIZONTAL
if __debug__: if __debug__:
from apps.debug import swipe_signal
swipe = await loop.race(Swipe(directions), swipe_signal()) swipe = await loop.race(Swipe(directions), swipe_signal())
else: else:
swipe = await Swipe(directions) swipe = await Swipe(directions)
@ -196,6 +195,8 @@ class InfoConfirm(ui.Layout):
return self.content.read_content() return self.content.read_content()
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:
from apps.debug import confirm_signal
return super().create_tasks() + (confirm_signal(),) return super().create_tasks() + (confirm_signal(),)
@ -273,4 +274,6 @@ class HoldToConfirm(ui.Layout):
return self.content.read_content() return self.content.read_content()
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:
from apps.debug import confirm_signal
return super().create_tasks() + (confirm_signal(),) return super().create_tasks() + (confirm_signal(),)

View File

@ -7,9 +7,6 @@ from .confirm import CANCELLED, CONFIRMED, Confirm
from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe from .swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
from .text import TEXT_MAX_LINES, Span, Text from .text import TEXT_MAX_LINES, Span, Text
if __debug__:
from apps.debug import confirm_signal, swipe_signal, notify_layout_change
_PAGINATED_LINE_WIDTH = const(204) _PAGINATED_LINE_WIDTH = const(204)
@ -81,6 +78,8 @@ class Paginated(ui.Layout):
directions = SWIPE_VERTICAL directions = SWIPE_VERTICAL
if __debug__: if __debug__:
from apps.debug import swipe_signal
swipe = await loop.race(Swipe(directions), swipe_signal()) swipe = await loop.race(Swipe(directions), swipe_signal())
else: else:
swipe = await Swipe(directions) swipe = await Swipe(directions)
@ -94,6 +93,8 @@ class Paginated(ui.Layout):
self.repaint = True self.repaint = True
if __debug__: if __debug__:
from apps.debug import notify_layout_change
notify_layout_change(self) notify_layout_change(self)
self.on_change() self.on_change()
@ -110,6 +111,8 @@ class Paginated(ui.Layout):
# shut down by a DebugLink confirm, even if used outside of a confirm() call # shut down by a DebugLink confirm, even if used outside of a confirm() call
# But we don't have any such usages in the codebase, and it doesn't actually # But we don't have any such usages in the codebase, and it doesn't actually
# make much sense to use a Paginated without a way to confirm it. # make much sense to use a Paginated without a way to confirm it.
from apps.debug import confirm_signal
return tasks + (confirm_signal(),) return tasks + (confirm_signal(),)
else: else:
return tasks return tasks
@ -230,6 +233,8 @@ class PaginatedWithButtons(ui.Layout):
return self.pages[self.page].read_content() return self.pages[self.page].read_content()
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:
from apps.debug import confirm_signal
return super().create_tasks() + (confirm_signal(),) return super().create_tasks() + (confirm_signal(),)