diff --git a/common/protob/messages-debug.proto b/common/protob/messages-debug.proto index db9d9dd55..6a1876c18 100644 --- a/common/protob/messages-debug.proto +++ b/common/protob/messages-debug.proto @@ -57,6 +57,33 @@ message DebugLinkRecordScreen { optional string target_directory = 1; // empty or missing to stop recording } +/** + * Request: Show text on the screen + * @start + * @next Success + */ + message DebugLinkShowText { + optional string header_text = 1; // screen header text + repeated DebugLinkShowTextItem body_text = 2; // body text segments + optional string header_icon = 3; // icon name in ui.style + optional string icon_color = 4; // color name in ui.style + + message DebugLinkShowTextItem { + optional DebugLinkShowTextStyle style = 1; + optional string content = 2; + } + + enum DebugLinkShowTextStyle { + NORMAL = 0; + BOLD = 1; + MONO = 2; + MONO_BOLD = 3; + BR = 4; + BR_HALF = 5; + SET_COLOR = 6; + } +} + /** * Request: Computer asks for device state * @start diff --git a/common/protob/messages.proto b/common/protob/messages.proto index a9c74fd63..38a39ed71 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -109,6 +109,7 @@ enum MessageType { MessageType_DebugLinkLayout = 9001 [(wire_debug_out) = true]; MessageType_DebugLinkReseedRandom = 9002 [(wire_debug_in) = true]; MessageType_DebugLinkRecordScreen = 9003 [(wire_debug_in) = true]; + MessageType_DebugLinkShowText = 9004 [(wire_debug_in) = true]; // Ethereum MessageType_EthereumGetPublicKey = 450 [(wire_in) = true]; diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py index 4f1004f98..93bdced3a 100644 --- a/core/src/apps/debug/__init__.py +++ b/core/src/apps/debug/__init__.py @@ -145,6 +145,7 @@ if __debug__: config.wipe() wire.add(MessageType.LoadDevice, __name__, "load_device") + wire.add(MessageType.DebugLinkShowText, __name__, "show_text") wire.register(MessageType.DebugLinkDecision, dispatch_DebugLinkDecision) wire.register(MessageType.DebugLinkGetState, dispatch_DebugLinkGetState) wire.register(MessageType.DebugLinkReseedRandom, dispatch_DebugLinkReseedRandom) diff --git a/core/src/apps/debug/show_text.py b/core/src/apps/debug/show_text.py new file mode 100644 index 000000000..7e8df5d27 --- /dev/null +++ b/core/src/apps/debug/show_text.py @@ -0,0 +1,50 @@ +import trezor.messages.DebugLinkShowTextStyle as S +from trezor import ui, wire +from trezor.messages.DebugLinkShowText import DebugLinkShowText +from trezor.messages.Success import Success +from trezor.ui import style, text +from trezor.ui.text import Text + +from apps.common.confirm import confirm + +STYLES = { + S.NORMAL: ui.NORMAL, + S.BOLD: ui.BOLD, + S.MONO: ui.MONO, + S.MONO_BOLD: ui.MONO_BOLD, + S.BR: text.BR, + S.BR_HALF: text.BR_HALF, +} + + +async def show_text(ctx: wire.Context, msg: DebugLinkShowText) -> Success: + if msg.header_icon is not None: + icon_name = "ICON_" + msg.header_icon + icon = getattr(style, icon_name) + if not isinstance(icon, str): + raise wire.DataError("Invalid icon name: {}".format(msg.header_icon)) + else: + icon = style.ICON_DEFAULT + + if msg.icon_color is not None: + color = getattr(style, msg.icon_color) + if not isinstance(color, int): + raise wire.DataError("Invalid color name: {}".format(msg.icon_color)) + else: + color = style.ORANGE_ICON + + dlg = Text(msg.header_text, icon, color, new_lines=False) + for item in msg.body_text: + if item.style in STYLES: + dlg.content.append(STYLES[item.style]) + elif item.style == S.SET_COLOR: + color = getattr(style, item.content) + if not isinstance(color, int): + raise wire.DataError("Invalid color name: {}".format(item.content)) + dlg.content.append(color) + + elif item.content is not None: + dlg.content.append(item.content) + + await confirm(ctx, dlg) + return Success("text shown") diff --git a/core/src/trezor/messages/DebugLinkShowText.py b/core/src/trezor/messages/DebugLinkShowText.py new file mode 100644 index 000000000..00a3a3f2f --- /dev/null +++ b/core/src/trezor/messages/DebugLinkShowText.py @@ -0,0 +1,37 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +from .DebugLinkShowTextItem import DebugLinkShowTextItem + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class DebugLinkShowText(p.MessageType): + MESSAGE_WIRE_TYPE = 9004 + + def __init__( + self, + header_text: str = None, + body_text: List[DebugLinkShowTextItem] = None, + header_icon: str = None, + icon_color: str = None, + ) -> None: + self.header_text = header_text + self.body_text = body_text if body_text is not None else [] + self.header_icon = header_icon + self.icon_color = icon_color + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('header_text', p.UnicodeType, 0), + 2: ('body_text', DebugLinkShowTextItem, p.FLAG_REPEATED), + 3: ('header_icon', p.UnicodeType, 0), + 4: ('icon_color', p.UnicodeType, 0), + } diff --git a/core/src/trezor/messages/DebugLinkShowTextItem.py b/core/src/trezor/messages/DebugLinkShowTextItem.py new file mode 100644 index 000000000..cf4e34881 --- /dev/null +++ b/core/src/trezor/messages/DebugLinkShowTextItem.py @@ -0,0 +1,29 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + EnumTypeDebugLinkShowTextStyle = Literal[0, 1, 2, 3, 4, 5, 6] + except ImportError: + pass + + +class DebugLinkShowTextItem(p.MessageType): + + def __init__( + self, + style: EnumTypeDebugLinkShowTextStyle = None, + content: str = None, + ) -> None: + self.style = style + self.content = content + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('style', p.EnumType("DebugLinkShowTextStyle", (0, 1, 2, 3, 4, 5, 6)), 0), + 2: ('content', p.UnicodeType, 0), + } diff --git a/core/src/trezor/messages/DebugLinkShowTextStyle.py b/core/src/trezor/messages/DebugLinkShowTextStyle.py new file mode 100644 index 000000000..6ddc6028f --- /dev/null +++ b/core/src/trezor/messages/DebugLinkShowTextStyle.py @@ -0,0 +1,12 @@ +# Automatically generated by pb2py +# fmt: off +if False: + from typing_extensions import Literal + +NORMAL = 0 # type: Literal[0] +BOLD = 1 # type: Literal[1] +MONO = 2 # type: Literal[2] +MONO_BOLD = 3 # type: Literal[3] +BR = 4 # type: Literal[4] +BR_HALF = 5 # type: Literal[5] +SET_COLOR = 6 # type: Literal[6] diff --git a/core/src/trezor/messages/MessageType.py b/core/src/trezor/messages/MessageType.py index 87fdc57a3..d2a40123a 100644 --- a/core/src/trezor/messages/MessageType.py +++ b/core/src/trezor/messages/MessageType.py @@ -76,6 +76,7 @@ DebugLinkFlashErase = 113 # type: Literal[113] DebugLinkLayout = 9001 # type: Literal[9001] DebugLinkReseedRandom = 9002 # type: Literal[9002] DebugLinkRecordScreen = 9003 # type: Literal[9003] +DebugLinkShowText = 9004 # type: Literal[9004] if not utils.BITCOIN_ONLY: EthereumGetPublicKey = 450 # type: Literal[450] EthereumPublicKey = 451 # type: Literal[451] diff --git a/docs/python/show-text-01.png b/docs/python/show-text-01.png new file mode 100644 index 000000000..cc7b7c10e Binary files /dev/null and b/docs/python/show-text-01.png differ diff --git a/docs/python/show-text-02.png b/docs/python/show-text-02.png new file mode 100644 index 000000000..cce2e128b Binary files /dev/null and b/docs/python/show-text-02.png differ diff --git a/docs/python/show-text-03.png b/docs/python/show-text-03.png new file mode 100644 index 000000000..f622b0d5c Binary files /dev/null and b/docs/python/show-text-03.png differ diff --git a/docs/python/show-text-04.png b/docs/python/show-text-04.png new file mode 100644 index 000000000..0eeb3f1ec Binary files /dev/null and b/docs/python/show-text-04.png differ diff --git a/docs/python/show-text-05.png b/docs/python/show-text-05.png new file mode 100644 index 000000000..8c3db1b86 Binary files /dev/null and b/docs/python/show-text-05.png differ diff --git a/docs/python/show-text-06.png b/docs/python/show-text-06.png new file mode 100644 index 000000000..ba4d98113 Binary files /dev/null and b/docs/python/show-text-06.png differ diff --git a/docs/python/show-text-07.png b/docs/python/show-text-07.png new file mode 100644 index 000000000..f91a4588f Binary files /dev/null and b/docs/python/show-text-07.png differ diff --git a/docs/python/show-text.md b/docs/python/show-text.md new file mode 100644 index 000000000..5b78fab11 --- /dev/null +++ b/docs/python/show-text.md @@ -0,0 +1,138 @@ +# Displaying text screens on Trezor T + +## Requirements + +For this feature, you will need a debug-enabled firmware for Trezor T. Usually that +will be the emulator. + +You will also need `trezorctl` 0.12 or later. Best bet is to run the version from github +master. + +## Trezor T text capabilities + +The Trezor T screen has a _header_, with an icon and text. Below it is _body_, which +can fit up to 5 lines of text. + +Text can be in one of several colors, and in one of four styles: NORMAL, BOLD, MONO +and MONO_BOLD. + +It is possible to have multiple styles on the same line, but different styles must be +separated by a space. + +I.e., this works fine: + +> This word is **bold** and this is `monospaced`. + +This will not work: + +> Em**bold**ened middle, mono`space`d middle. + +A line that is too long to fit on screen will automatically break, using a hyphen (`-`) +character. This is usually undesirable. Instead, a manual line-break should be inserted +in the appropriate place. + +## Command line syntax + +The most basic way to put words on screen is this: + +```sh +trezorctl debug show-text "My hovercraft is full of eels, call the porter, there is a frog in my bidet." +``` + +The above command will show: + +![Screenshot01](show-text-01.png) + +Notice the "quotes" around the text. The whole body text must be enquoted. + +To use quotes inside the text, prefix them with backslashes `\`: + +```sh +trezorctl debug show-text "My \"hovercraft\" is full of eels." +``` + +![Screenshot02](show-text-02.png) + +### Line breaks + +Let's insert some line breaks. Do that by placing `@@BR` in the appropriate place +in the text: + +```sh +trezorctl debug show-text "My hovercraft is full of @@BR eels, call the porter, @@BR there is a frog in my @@BR bidet." +``` + +![Screenshot03](show-text-03.png) + +Better! + +### Text styles + +Now let's add some style. Use `@@BOLD` to start printing in bold. Use `@@NORMAL` +to go back to normal text. `@@MONO` and `@@MONO_BOLD` works similarly. + +```sh +trezorctl debug show-text "My hovercraft is @@BOLD full of @@BR eels. @@NORMAL Call the porter, @@BR there is a @@MONO frog @@NORMAL in my @@BR bidet." +``` + +![Screenshot04](show-text-04.png) + +### Line spacing + +Adding another `@@BR` after a `@@BR` will leave one line empty -- just like pressing +\ twice. + +If you don't want a full empty line, you can make a half-break with `@@BR_HALF`. + +```sh +trezorctl debug show-text "Line one. @@BR @@BR Line two. @@BR @@BR_HALF Line three." +``` + +![Screenshot05](show-text-05.png) + + +### Text colors + +To switch to one of the [available colors](../../core/src/trezor/ui/style.py#L14-L47), +use the color name prefixed with `%%`: e.g., `%%RED`, `%%LIGHT_BLUE`... + +To switch back to the default color, you can use `%%FG`: + +```sh +trezorctl debug show-text "My %%RED hovercraft is @@BOLD full %%GREEN of @@BR eels. @@NORMAL Call %%ORANGE the %%FG porter." +``` + +![Screenshot06](show-text-06.png) + +### Headers + +The default header says "Showing text" with an orange gear icon. It is possible to +change all of that. + +To change the text, use `-h` option: + +```sh +trezorctl debug show-text -h "Hello world" "My hovercraft is full." +``` + +To change the icon, you can pick [an icon name from here](../../core/src/trezor/ui/style.py#L50-L70) and specify it with the `-i` option: + +```sh +trezorctl debug show-text -i RECEIVE "My hovercraft is full." +``` + +The icons are defined as shapes, and you can specify a custom color [from the list](../../core/src/trezor/ui/style.py#L14-L47) with the `-c` option: + +```sh +trezorctl debug show-text -c RED "My hovercraft is full." +``` + +### Putting it all together + +Here is how to reproduce the confirmation screen after the wallet is created: + +```sh +trezorctl debug show-text -h "Success" -i CONFIRM -c GREEN "@@BOLD New wallet created @@BR successfully! @@BR @@BR_HALF @@NORMAL You should back up your @@BR new wallet right now." +``` + +![Screenshot07](show-text-07.png) diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile index 72ce15436..3ebe6dd30 100644 --- a/legacy/firmware/protob/Makefile +++ b/legacy/firmware/protob/Makefile @@ -2,7 +2,8 @@ ifneq ($(V),1) Q := @ endif -SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn DebugLinkRecordScreen DebugLinkReseedRandom +SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn \ + DebugLinkRecordScreen DebugLinkReseedRandom DebugLinkShowText ifeq ($(BITCOIN_ONLY), 1) SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar diff --git a/legacy/firmware/protob/messages-debug.options b/legacy/firmware/protob/messages-debug.options index a5b7a122c..496ed2278 100644 --- a/legacy/firmware/protob/messages-debug.options +++ b/legacy/firmware/protob/messages-debug.options @@ -17,4 +17,3 @@ DebugLinkMemoryWrite.memory max_size:1024 # unused fields DebugLinkState.layout_lines max_count:10 max_size:30 DebugLinkLayout.lines max_count:10 max_size:30 -DebugLinkRecordScreen.target_directory max_size:16 diff --git a/python/src/trezorlib/cli/debug.py b/python/src/trezorlib/cli/debug.py new file mode 100644 index 000000000..379cd7243 --- /dev/null +++ b/python/src/trezorlib/cli/debug.py @@ -0,0 +1,73 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import click + +from .. import debuglink +from ..messages import DebugLinkShowTextStyle as S + + +@click.group(name="debug") +def cli(): + """Miscellaneous debug features.""" + + +STYLES = { + "@@NORMAL": S.NORMAL, + "@@BOLD": S.BOLD, + "@@MONO": S.MONO, + "@@MONO_BOLD": S.MONO_BOLD, + "@@BR": S.BR, + "@@BR_HALF": S.BR_HALF, +} + + +@cli.command() +@click.option("-i", "--icon", help="Header icon name") +@click.option("-c", "--color", help="Header icon color") +@click.option("-h", "--header", help="Header text", default="Showing text") +@click.argument("body") +@click.pass_obj +def show_text(connect, icon, color, header, body): + """Show text on Trezor display. + + For usage instructions, see: + https://github.com/trezor/trezor-firmware/blob/master/docs/python/show-text.md + """ + body = body.split() + body_text = [] + words = [] + + def _flush(): + if words: + body_text.append((None, " ".join(words))) + words.clear() + + for word in body: + if word in STYLES: + _flush() + body_text.append((STYLES[word], None)) + elif word.startswith("%%"): + _flush() + body_text.append((S.SET_COLOR, word[2:])) + else: + words.append(word) + + _flush() + + return debuglink.show_text( + connect(), header, body_text, icon=icon, icon_color=color + ) diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index 95878fd9d..baf9a28a0 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -33,6 +33,7 @@ from . import ( cardano, cosi, crypto, + debug, device, eos, ethereum, @@ -59,6 +60,7 @@ COMMAND_ALIASES = { "sd-protect": device.sd_protect, "load-device": device.load, "self-test": device.self_test, + "show-text": debug.show_text, "get-entropy": crypto.get_entropy, "encrypt-keyvalue": crypto.encrypt_keyvalue, "decrypt-keyvalue": crypto.decrypt_keyvalue, @@ -296,7 +298,7 @@ cli.add_command(stellar.cli) cli.add_command(tezos.cli) cli.add_command(firmware.firmware_update) - +cli.add_command(debug.cli) # # Main diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py index 4e7d4e539..d88043d53 100644 --- a/python/src/trezorlib/debuglink.py +++ b/python/src/trezorlib/debuglink.py @@ -510,3 +510,18 @@ def self_test(client): payload=b"\x00\xFF\x55\xAA\x66\x99\x33\xCCABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\x00\xFF\x55\xAA\x66\x99\x33\xCC" ) ) + + +@expect(proto.Success, field="message") +def show_text(client, header_text, body_text, icon=None, icon_color=None): + body_text = [ + proto.DebugLinkShowTextItem(style=style, content=content) + for style, content in body_text + ] + msg = proto.DebugLinkShowText( + header_text=header_text, + body_text=body_text, + header_icon=icon, + icon_color=icon_color, + ) + return client.call(msg) diff --git a/python/src/trezorlib/messages/DebugLinkShowText.py b/python/src/trezorlib/messages/DebugLinkShowText.py new file mode 100644 index 000000000..aaa0f6581 --- /dev/null +++ b/python/src/trezorlib/messages/DebugLinkShowText.py @@ -0,0 +1,37 @@ +# Automatically generated by pb2py +# fmt: off +from .. import protobuf as p + +from .DebugLinkShowTextItem import DebugLinkShowTextItem + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class DebugLinkShowText(p.MessageType): + MESSAGE_WIRE_TYPE = 9004 + + def __init__( + self, + header_text: str = None, + body_text: List[DebugLinkShowTextItem] = None, + header_icon: str = None, + icon_color: str = None, + ) -> None: + self.header_text = header_text + self.body_text = body_text if body_text is not None else [] + self.header_icon = header_icon + self.icon_color = icon_color + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('header_text', p.UnicodeType, 0), + 2: ('body_text', DebugLinkShowTextItem, p.FLAG_REPEATED), + 3: ('header_icon', p.UnicodeType, 0), + 4: ('icon_color', p.UnicodeType, 0), + } diff --git a/python/src/trezorlib/messages/DebugLinkShowTextItem.py b/python/src/trezorlib/messages/DebugLinkShowTextItem.py new file mode 100644 index 000000000..55cb1bd9b --- /dev/null +++ b/python/src/trezorlib/messages/DebugLinkShowTextItem.py @@ -0,0 +1,29 @@ +# Automatically generated by pb2py +# fmt: off +from .. import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + EnumTypeDebugLinkShowTextStyle = Literal[0, 1, 2, 3, 4, 5, 6] + except ImportError: + pass + + +class DebugLinkShowTextItem(p.MessageType): + + def __init__( + self, + style: EnumTypeDebugLinkShowTextStyle = None, + content: str = None, + ) -> None: + self.style = style + self.content = content + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('style', p.EnumType("DebugLinkShowTextStyle", (0, 1, 2, 3, 4, 5, 6)), 0), + 2: ('content', p.UnicodeType, 0), + } diff --git a/python/src/trezorlib/messages/DebugLinkShowTextStyle.py b/python/src/trezorlib/messages/DebugLinkShowTextStyle.py new file mode 100644 index 000000000..6ddc6028f --- /dev/null +++ b/python/src/trezorlib/messages/DebugLinkShowTextStyle.py @@ -0,0 +1,12 @@ +# Automatically generated by pb2py +# fmt: off +if False: + from typing_extensions import Literal + +NORMAL = 0 # type: Literal[0] +BOLD = 1 # type: Literal[1] +MONO = 2 # type: Literal[2] +MONO_BOLD = 3 # type: Literal[3] +BR = 4 # type: Literal[4] +BR_HALF = 5 # type: Literal[5] +SET_COLOR = 6 # type: Literal[6] diff --git a/python/src/trezorlib/messages/MessageType.py b/python/src/trezorlib/messages/MessageType.py index f66649b06..bc8df2258 100644 --- a/python/src/trezorlib/messages/MessageType.py +++ b/python/src/trezorlib/messages/MessageType.py @@ -74,6 +74,7 @@ DebugLinkFlashErase = 113 # type: Literal[113] DebugLinkLayout = 9001 # type: Literal[9001] DebugLinkReseedRandom = 9002 # type: Literal[9002] DebugLinkRecordScreen = 9003 # type: Literal[9003] +DebugLinkShowText = 9004 # type: Literal[9004] EthereumGetPublicKey = 450 # type: Literal[450] EthereumPublicKey = 451 # type: Literal[451] EthereumGetAddress = 56 # type: Literal[56] diff --git a/python/src/trezorlib/messages/__init__.py b/python/src/trezorlib/messages/__init__.py index bd1bfa8e3..8fdb40269 100644 --- a/python/src/trezorlib/messages/__init__.py +++ b/python/src/trezorlib/messages/__init__.py @@ -49,6 +49,8 @@ from .DebugLinkMemoryRead import DebugLinkMemoryRead from .DebugLinkMemoryWrite import DebugLinkMemoryWrite from .DebugLinkRecordScreen import DebugLinkRecordScreen from .DebugLinkReseedRandom import DebugLinkReseedRandom +from .DebugLinkShowText import DebugLinkShowText +from .DebugLinkShowTextItem import DebugLinkShowTextItem from .DebugLinkState import DebugLinkState from .DebugLinkStop import DebugLinkStop from .DebugMoneroDiagAck import DebugMoneroDiagAck @@ -270,6 +272,7 @@ from . import BinanceOrderType from . import BinanceTimeInForce from . import ButtonRequestType from . import Capability +from . import DebugLinkShowTextStyle from . import DebugSwipeDirection from . import FailureType from . import InputScriptType