-
-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Related #1026 This PR adds an API endpoint that accepts text, parses each text character to a HID keystroke, and asynchronously sends all keystrokes to the target machine. ### Notes 1. By writing the keystrokes in a background thread, separate from main thread that runs the HTTP server and SocketIO server, we avoid blocking HTTP requests and WebSocket messages ([that unexpectedly disconnected the client during a large paste](#1026)). * The background thread that writes the pasted text, is run in a "fire and forget" manner which means we no longer report on the success or failure of the HID file IO. However, a single write error will abort further keystrokes from being written. ### Peer testing You can test this PR on device via the following stacked PR: * #1626 <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1625"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a>
- Loading branch information
1 parent
3a7fe4a
commit da6f5f0
Showing
8 changed files
with
195 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import text_to_hid | ||
from request_parsers import errors | ||
from request_parsers import json | ||
|
||
|
||
def parse_keystrokes(request): | ||
""" | ||
Parses HID keystrokes from the request. | ||
Args: | ||
request: Flask request with the following fields in the JSON body: | ||
(str) text | ||
(str) language | ||
Returns: | ||
A list of HID keystrokes. | ||
Raises: | ||
UnsupportedPastedCharacterError: If a pasted character cannot to be | ||
converted to a HID keystroke. | ||
""" | ||
# pylint: disable=unbalanced-tuple-unpacking | ||
(text, | ||
language) = json.parse_json_body(request, | ||
required_fields=['text', 'language']) | ||
keystrokes = [] | ||
# Preserve the ordering of any unsupported characters found. | ||
unsupported_chars_found = {} | ||
for char in text: | ||
try: | ||
keystroke = text_to_hid.convert(char, language) | ||
except text_to_hid.UnsupportedCharacterError: | ||
unsupported_chars_found[char] = True | ||
continue | ||
# Skip ignored characters. | ||
if keystroke is None: | ||
continue | ||
keystrokes.append(keystroke) | ||
if unsupported_chars_found: | ||
raise errors.UnsupportedPastedCharacterError( | ||
f'These characters are not supported: ' | ||
f'{", ".join(map(repr, unsupported_chars_found.keys()))}') | ||
|
||
return keystrokes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import unittest | ||
from unittest import mock | ||
|
||
from hid import keycodes as hid | ||
from request_parsers import errors | ||
from request_parsers import paste | ||
|
||
|
||
def make_mock_request(json_data): | ||
mock_request = mock.Mock() | ||
mock_request.get_json.return_value = json_data | ||
return mock_request | ||
|
||
|
||
class KeystrokesParserTest(unittest.TestCase): | ||
|
||
def test_accepts(self): | ||
self.assertEqual([ | ||
hid.Keystroke(keycode=hid.KEYCODE_A), | ||
hid.Keystroke(keycode=hid.KEYCODE_B), | ||
hid.Keystroke(keycode=hid.KEYCODE_C), | ||
], | ||
paste.parse_keystrokes( | ||
make_mock_request({ | ||
'text': 'abc', | ||
'language': 'en-US' | ||
}))) | ||
|
||
def test_rejects_unsupported_character(self): | ||
with self.assertRaises(errors.UnsupportedPastedCharacterError) as ctx: | ||
paste.parse_keystrokes( | ||
make_mock_request({ | ||
'text': 'Monday–Friday', | ||
'language': 'en-US' | ||
})) | ||
self.assertEqual("""These characters are not supported: '–'""", | ||
str(ctx.exception)) | ||
|
||
def test_rejects_unsupported_characters_preserving_order(self): | ||
with self.assertRaises(errors.UnsupportedPastedCharacterError) as ctx: | ||
paste.parse_keystrokes( | ||
make_mock_request({ | ||
'text': '“Hello, World!” — Programmer', | ||
'language': 'en-US' | ||
})) | ||
self.assertEqual( | ||
"""These characters are not supported: '“', '”', '—'""", | ||
str(ctx.exception)) | ||
|
||
def test_skips_ignored_character(self): | ||
self.assertEqual([ | ||
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_1), | ||
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_2), | ||
hid.Keystroke(keycode=hid.KEYCODE_ENTER), | ||
hid.Keystroke(keycode=hid.KEYCODE_NUMBER_3), | ||
], | ||
paste.parse_keystrokes( | ||
make_mock_request({ | ||
'text': '12\r\n3', | ||
'language': 'en-US' | ||
}))) |