-
Notifications
You must be signed in to change notification settings - Fork 111
Add Keyboard class with API to send HID reports; prune constants; change examples #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
01366a7
adde1dd
a877d7d
96d1288
a50f0f0
4a883fa
a129217
9860fe9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,8 @@ Introduction | |
:target: https://gitter.im/adafruit/circuitpython?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge | ||
:alt: Gitter | ||
|
||
This driver provides USB HID related constants. In the future it will include | ||
helper functions and classes as well. | ||
This driver simulates USB HID devices, such as keyboard, mouse, and joystick. | ||
Right now only keyboard is implemented. | ||
|
||
Dependencies | ||
============= | ||
|
@@ -26,39 +26,53 @@ This is easily achieved by downloading | |
Usage Example | ||
============= | ||
|
||
The current `keyboard` module stores key constants which make it easier to | ||
construct keypress reports for a keyboard device. | ||
The ``keyboard`` module defines keycode constants and a ``Keyboard`` class to | ||
construct and send keypress reports for a USB keyboard device. | ||
|
||
.. code-block:: python | ||
|
||
import usb_hid | ||
import adafruit_hid.keyboard as kbd | ||
import time | ||
|
||
report = bytearray(8) # Keyboard reports are always 8 bytes. | ||
|
||
# Devices are initialized earlier so find the one for the keyboard. | ||
keyboard = None | ||
for device in usb_hid.devices: | ||
if device.usage_page is 0x1 and device.usage is 0x06: | ||
keyboard = device | ||
break | ||
|
||
# The first byte of the report includes a bitfield indicating which | ||
# modifiers are pressed. Their bit position is their code's difference from | ||
# 0xE0. | ||
report[0] |= 1 << (kbd.LEFT_SHIFT - 0xE0) | ||
# Normal keys are simply their byte code in bytes 2-7. When fewer than six | ||
# keys are pressed then the trailing bytes are zero. | ||
report[2] = kbd.A | ||
keyboard.send_report(report) | ||
|
||
time.sleep(0.1) | ||
|
||
# Clear the key presses and send another report. | ||
report[0] = 0 | ||
report[2] = 0 | ||
keyboard.send_report(report) | ||
from adafruit_hid.keyboard import Keyboard | ||
|
||
# Find a keyboard device to talk to. | ||
kbd = Keyboard() | ||
|
||
# Simulate typing. Press and release the A key (not shifted), | ||
# then the B key, then c, then the Enter key. | ||
# Type "abc" followed by return. | ||
kbd.type("abc\n") | ||
|
||
# Type control-x, then "Abc", then backspace. | ||
kbd.type((Keyboard.CONTROL, 'x'), 'Abc', Keyboard.BACKSPACE) | ||
|
||
# Press and hold left-hand Control and right-hand Alt. | ||
kbd.press_keys(Keyboard.CONTROL, Keyboard.ALT) | ||
|
||
# Press and hold the A and B keys (lower case, not shifted). | ||
kbd.press_keys('ab') | ||
|
||
# Press and hold the A and B keys (same effect as above). | ||
kbd.press_keys('a', 'b') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having two ways to do this seems weird to me. I'd rather have them take keycode constants consistently to make the API uniform and reinforce the idea that they are just codes that may result in any letter. I also think you can drop the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can remove that generality of multiple strings. It is confusing to have too many ways to do something. Also I'll drop the |
||
|
||
# Press capital C. This implies pressing left Shift as well, | ||
# because the character is capitalized. | ||
kbd.press_keys('C') | ||
|
||
# Release the B key. | ||
kbd.release_keys('b') | ||
|
||
# Release all keys. | ||
kbd.release_all() | ||
|
||
# Press '5' on the keypad and the F8 key. | ||
kbd.press_keys(Keyboard.KEYPAD_FIVE, Keyboard.F8) | ||
|
||
# Press the shifted '1' key to get '!' (exclamation mark). | ||
kbd.press_keys(Keyboard.SHIFT, '1') | ||
|
||
# Same effect as above. The '!' implies pressing Shift and '1'. | ||
kbd.press_keys('!'). | ||
|
||
|
||
|
||
Contributing | ||
============ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about splitting the
type
method out into its own module? It feels to me like a convenience on top of thepress
andrelease
mechanics. Splitting it would provide a clear boundary wherepress
andrelease
can use keycode constants andtype
can use strings.In general, I like many smaller modules so that people can import just the functionality they want. It also helps with RAM usage during load because each file is smaller individually.
Separately,
type
can then get fancier for those who need it to type something specific. For example, you could actually handle keyboard layouts liketype("Quickly", layout=en_US)
and have it press the right underlying keys.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the package providing three pieces of functionality:
If the user is doing #1 but not #3, s/he will still need to do #2 if the program doesn't know until run-time what it is going to type. I like your idea of possible future alternate layouts. The Arduino library is very US-keyboard-centric.
Originally I wrote the
_char_to_keycode()
method to return a two-element tuple of(modifier, keycode)
. I took that out because it allocated storage and I wanted to avoid that. TheASCII_TO_KEYCODE
table is only 128 bytes because of the bit-encoding for shift, and it can live in flash, but it only works for English.But the general case is a table of tuples, or similar. A Unicode character may require multiple keypresses and releases with modifiers. For instance, on a Mac, to type
è
, you can typeOption
+`
thene
. That might be represented as((OPTION, GRAVE_ACCENT), E)
or even((OPTION, GRAVE_ACCENT), (E,))
where each top-level element is a combination to press and release. Or if you used dead keys (as on Windows, I think), it could be(GRAVE_ACCENT, E)
.So maybe I should give up on the idea of not generating garbage and not using RAM for the lookup tables. There's an efficiency/generality tradeoff here that's difficult given the tight storage constraints. I would love to have constant tuples and dicts in flash, but that may be a long way off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its fine to use RAM for the lookup tables. I just would make sure and only load the lookup table(s) the user's code needs.
Its good to think about efficiency but we shouldn't be hamstrung by it. MicroPython and CircuitPython's strength is ease of use and fast iteration not speed. I think its good to figure about good APIs first and worry about speed second. There are a lot of tricks we can do with a little effort to provide the same API but make it faster or smaller. For example, we could provide a frozendict C implementation that stores the dictionary with a continuous block rather than a bunch of small chunks.
Perhaps each locale should provide a function to do the mapping and then internally decide the most efficient way to do the conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like the right idea. I'll try to come up with a uniform API.