├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE │ └── adafruit_circuitpython_pr.md └── workflows │ ├── build.yml │ ├── failure-help-text.yml │ ├── release_gh.yml │ └── release_pypi.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── LICENSES ├── CC-BY-4.0.txt ├── MIT.txt └── Unlicense.txt ├── README.rst ├── README.rst.license ├── adafruit_hid ├── __init__.py ├── consumer_control.py ├── consumer_control_code.py ├── keyboard.py ├── keyboard_layout_base.py ├── keyboard_layout_us.py ├── keycode.py └── mouse.py ├── docs ├── _static │ ├── favicon.ico │ └── favicon.ico.license ├── api.rst ├── api.rst.license ├── conf.py ├── examples.rst ├── examples.rst.license ├── index.rst ├── index.rst.license └── requirements.txt ├── examples ├── hid_consumer_control_brightness.py ├── hid_gamepad.py ├── hid_joywing_gamepad.py ├── hid_keyboard_layout.py ├── hid_keyboard_shortcuts.py ├── hid_simple_gamepad.py └── hid_simpletest.py ├── optional_requirements.txt ├── pyproject.toml ├── requirements.txt └── ruff.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | .py text eol=lf 6 | .rst text eol=lf 7 | .txt text eol=lf 8 | .yaml text eol=lf 9 | .toml text eol=lf 10 | .license text eol=lf 11 | .md text eol=lf 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | Thank you for contributing! Before you submit a pull request, please read the following. 6 | 7 | Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html 8 | 9 | If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs 10 | 11 | Before submitting the pull request, make sure you've run Pylint and Black locally on your code. You can do this manually or using pre-commit. Instructions are available here: https://adafru.it/check-your-code 12 | 13 | Please remove all of this text before submitting. Include an explanation or list of changes included in your PR, as well as, if applicable, a link to any related issues. 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Build CI 6 | 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Run Build CI workflow 14 | uses: adafruit/workflows-circuitpython-libs/build@main 15 | -------------------------------------------------------------------------------- /.github/workflows/failure-help-text.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Failure help text 6 | 7 | on: 8 | workflow_run: 9 | workflows: ["Build CI"] 10 | types: 11 | - completed 12 | 13 | jobs: 14 | post-help: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} 17 | steps: 18 | - name: Post comment to help 19 | uses: adafruit/circuitpython-action-library-ci-failed@v1 20 | -------------------------------------------------------------------------------- /.github/workflows/release_gh.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: GitHub Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run GitHub Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-gh@main 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | upload-url: ${{ github.event.release.upload_url }} 20 | -------------------------------------------------------------------------------- /.github/workflows/release_pypi.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: PyPI Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run PyPI Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-pypi@main 17 | with: 18 | pypi-username: ${{ secrets.pypi_username }} 19 | pypi-password: ${{ secrets.pypi_password }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # Do not include files and directories created by your personal work environment, such as the IDE 6 | # you use, except for those already listed here. Pull requests including changes to this file will 7 | # not be accepted. 8 | 9 | # This .gitignore file contains rules for files generated by working with CircuitPython libraries, 10 | # including building Sphinx, testing with pip, and creating a virual environment, as well as the 11 | # MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. 12 | 13 | # If you find that there are files being generated on your machine that should not be included in 14 | # your git commit, you should create a .gitignore_global file on your computer to include the 15 | # files created by your personal setup. To do so, follow the two steps below. 16 | 17 | # First, create a file called .gitignore_global somewhere convenient for you, and add rules for 18 | # the files you want to exclude from git commits. 19 | 20 | # Second, configure Git to use the exclude file for all Git repositories by running the 21 | # following via commandline, replacing "path/to/your/" with the actual path to your newly created 22 | # .gitignore_global file: 23 | # git config --global core.excludesfile path/to/your/.gitignore_global 24 | 25 | # CircuitPython-specific files 26 | *.mpy 27 | 28 | # Python-specific files 29 | __pycache__ 30 | *.pyc 31 | 32 | # Sphinx build-specific files 33 | _build 34 | 35 | # This file results from running `pip -e install .` in a local repository 36 | *.egg-info 37 | 38 | # Virtual environment-specific files 39 | .env 40 | .venv 41 | 42 | # MacOS-specific files 43 | *.DS_Store 44 | 45 | # IDE-specific files 46 | .idea 47 | .vscode 48 | *~ 49 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.5.0 8 | hooks: 9 | - id: check-yaml 10 | - id: end-of-file-fixer 11 | - id: trailing-whitespace 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.3.4 14 | hooks: 15 | - id: ruff-format 16 | - id: ruff 17 | args: ["--fix"] 18 | - repo: https://github.com/fsfe/reuse-tool 19 | rev: v3.0.1 20 | hooks: 21 | - id: reuse 22 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | # Read the Docs configuration file 6 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 7 | 8 | # Required 9 | version: 2 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | 14 | build: 15 | os: ubuntu-20.04 16 | tools: 17 | python: "3" 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | - requirements: requirements.txt 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Adafruit Community Code of Conduct 8 | 9 | ## Our Pledge 10 | 11 | In the interest of fostering an open and welcoming environment, we as 12 | contributors and leaders pledge to making participation in our project and 13 | our community a harassment-free experience for everyone, regardless of age, body 14 | size, disability, ethnicity, gender identity and expression, level or type of 15 | experience, education, socio-economic status, nationality, personal appearance, 16 | race, religion, or sexual identity and orientation. 17 | 18 | ## Our Standards 19 | 20 | We are committed to providing a friendly, safe and welcoming environment for 21 | all. 22 | 23 | Examples of behavior that contributes to creating a positive environment 24 | include: 25 | 26 | * Be kind and courteous to others 27 | * Using welcoming and inclusive language 28 | * Being respectful of differing viewpoints and experiences 29 | * Collaborating with other community members 30 | * Gracefully accepting constructive criticism 31 | * Focusing on what is best for the community 32 | * Showing empathy towards other community members 33 | 34 | Examples of unacceptable behavior by participants include: 35 | 36 | * The use of sexualized language or imagery and sexual attention or advances 37 | * The use of inappropriate images, including in a community member's avatar 38 | * The use of inappropriate language, including in a community member's nickname 39 | * Any spamming, flaming, baiting or other attention-stealing behavior 40 | * Excessive or unwelcome helping; answering outside the scope of the question 41 | asked 42 | * Trolling, insulting/derogatory comments, and personal or political attacks 43 | * Promoting or spreading disinformation, lies, or conspiracy theories against 44 | a person, group, organisation, project, or community 45 | * Public or private harassment 46 | * Publishing others' private information, such as a physical or electronic 47 | address, without explicit permission 48 | * Other conduct which could reasonably be considered inappropriate 49 | 50 | The goal of the standards and moderation guidelines outlined here is to build 51 | and maintain a respectful community. We ask that you don’t just aim to be 52 | "technically unimpeachable", but rather try to be your best self. 53 | 54 | We value many things beyond technical expertise, including collaboration and 55 | supporting others within our community. Providing a positive experience for 56 | other community members can have a much more significant impact than simply 57 | providing the correct answer. 58 | 59 | ## Our Responsibilities 60 | 61 | Project leaders are responsible for clarifying the standards of acceptable 62 | behavior and are expected to take appropriate and fair corrective action in 63 | response to any instances of unacceptable behavior. 64 | 65 | Project leaders have the right and responsibility to remove, edit, or 66 | reject messages, comments, commits, code, issues, and other contributions 67 | that are not aligned to this Code of Conduct, or to ban temporarily or 68 | permanently any community member for other behaviors that they deem 69 | inappropriate, threatening, offensive, or harmful. 70 | 71 | ## Moderation 72 | 73 | Instances of behaviors that violate the Adafruit Community Code of Conduct 74 | may be reported by any member of the community. Community members are 75 | encouraged to report these situations, including situations they witness 76 | involving other community members. 77 | 78 | You may report in the following ways: 79 | 80 | In any situation, you may send an email to . 81 | 82 | On the Adafruit Discord, you may send an open message from any channel 83 | to all Community Moderators by tagging @community moderators. You may 84 | also send an open message from any channel, or a direct message to 85 | @kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, 86 | @sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175. 87 | 88 | Email and direct message reports will be kept confidential. 89 | 90 | In situations on Discord where the issue is particularly egregious, possibly 91 | illegal, requires immediate action, or violates the Discord terms of service, 92 | you should also report the message directly to Discord. 93 | 94 | These are the steps for upholding our community’s standards of conduct. 95 | 96 | 1. Any member of the community may report any situation that violates the 97 | Adafruit Community Code of Conduct. All reports will be reviewed and 98 | investigated. 99 | 2. If the behavior is an egregious violation, the community member who 100 | committed the violation may be banned immediately, without warning. 101 | 3. Otherwise, moderators will first respond to such behavior with a warning. 102 | 4. Moderators follow a soft "three strikes" policy - the community member may 103 | be given another chance, if they are receptive to the warning and change their 104 | behavior. 105 | 5. If the community member is unreceptive or unreasonable when warned by a 106 | moderator, or the warning goes unheeded, they may be banned for a first or 107 | second offense. Repeated offenses will result in the community member being 108 | banned. 109 | 110 | ## Scope 111 | 112 | This Code of Conduct and the enforcement policies listed above apply to all 113 | Adafruit Community venues. This includes but is not limited to any community 114 | spaces (both public and private), the entire Adafruit Discord server, and 115 | Adafruit GitHub repositories. Examples of Adafruit Community spaces include 116 | but are not limited to meet-ups, audio chats on the Adafruit Discord, or 117 | interaction at a conference. 118 | 119 | This Code of Conduct applies both within project spaces and in public spaces 120 | when an individual is representing the project or its community. As a community 121 | member, you are representing our community, and are expected to behave 122 | accordingly. 123 | 124 | ## Attribution 125 | 126 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 127 | version 1.4, available at 128 | , 129 | and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). 130 | 131 | For other projects adopting the Adafruit Community Code of 132 | Conduct, please contact the maintainers of those projects for enforcement. 133 | If you wish to use this code of conduct for your own project, consider 134 | explicitly mentioning your moderation policy or making a copy with your 135 | own moderation policy so as to avoid confusion. 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Scott Shawcroft for Adafruit Industries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Creative Commons Corporation 2 | ("Creative Commons") is not a law firm and does not provide legal services 3 | or legal advice. Distribution of Creative Commons public licenses does not 4 | create a lawyer-client or other relationship. Creative Commons makes its licenses 5 | and related information available on an "as-is" basis. Creative Commons gives 6 | no warranties regarding its licenses, any material licensed under their terms 7 | and conditions, or any related information. Creative Commons disclaims all 8 | liability for damages resulting from their use to the fullest extent possible. 9 | 10 | Using Creative Commons Public Licenses 11 | 12 | Creative Commons public licenses provide a standard set of terms and conditions 13 | that creators and other rights holders may use to share original works of 14 | authorship and other material subject to copyright and certain other rights 15 | specified in the public license below. The following considerations are for 16 | informational purposes only, are not exhaustive, and do not form part of our 17 | licenses. 18 | 19 | Considerations for licensors: Our public licenses are intended for use by 20 | those authorized to give the public permission to use material in ways otherwise 21 | restricted by copyright and certain other rights. Our licenses are irrevocable. 22 | Licensors should read and understand the terms and conditions of the license 23 | they choose before applying it. Licensors should also secure all rights necessary 24 | before applying our licenses so that the public can reuse the material as 25 | expected. Licensors should clearly mark any material not subject to the license. 26 | This includes other CC-licensed material, or material used under an exception 27 | or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors 28 | 29 | Considerations for the public: By using one of our public licenses, a licensor 30 | grants the public permission to use the licensed material under specified 31 | terms and conditions. If the licensor's permission is not necessary for any 32 | reason–for example, because of any applicable exception or limitation to copyright–then 33 | that use is not regulated by the license. Our licenses grant only permissions 34 | under copyright and certain other rights that a licensor has authority to 35 | grant. Use of the licensed material may still be restricted for other reasons, 36 | including because others have copyright or other rights in the material. A 37 | licensor may make special requests, such as asking that all changes be marked 38 | or described. Although not required by our licenses, you are encouraged to 39 | respect those requests where reasonable. More considerations for the public 40 | : wiki.creativecommons.org/Considerations_for_licensees Creative Commons Attribution 41 | 4.0 International Public License 42 | 43 | By exercising the Licensed Rights (defined below), You accept and agree to 44 | be bound by the terms and conditions of this Creative Commons Attribution 45 | 4.0 International Public License ("Public License"). To the extent this Public 46 | License may be interpreted as a contract, You are granted the Licensed Rights 47 | in consideration of Your acceptance of these terms and conditions, and the 48 | Licensor grants You such rights in consideration of benefits the Licensor 49 | receives from making the Licensed Material available under these terms and 50 | conditions. 51 | 52 | Section 1 – Definitions. 53 | 54 | a. Adapted Material means material subject to Copyright and Similar Rights 55 | that is derived from or based upon the Licensed Material and in which the 56 | Licensed Material is translated, altered, arranged, transformed, or otherwise 57 | modified in a manner requiring permission under the Copyright and Similar 58 | Rights held by the Licensor. For purposes of this Public License, where the 59 | Licensed Material is a musical work, performance, or sound recording, Adapted 60 | Material is always produced where the Licensed Material is synched in timed 61 | relation with a moving image. 62 | 63 | b. Adapter's License means the license You apply to Your Copyright and Similar 64 | Rights in Your contributions to Adapted Material in accordance with the terms 65 | and conditions of this Public License. 66 | 67 | c. Copyright and Similar Rights means copyright and/or similar rights closely 68 | related to copyright including, without limitation, performance, broadcast, 69 | sound recording, and Sui Generis Database Rights, without regard to how the 70 | rights are labeled or categorized. For purposes of this Public License, the 71 | rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 72 | 73 | d. Effective Technological Measures means those measures that, in the absence 74 | of proper authority, may not be circumvented under laws fulfilling obligations 75 | under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, 76 | and/or similar international agreements. 77 | 78 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other 79 | exception or limitation to Copyright and Similar Rights that applies to Your 80 | use of the Licensed Material. 81 | 82 | f. Licensed Material means the artistic or literary work, database, or other 83 | material to which the Licensor applied this Public License. 84 | 85 | g. Licensed Rights means the rights granted to You subject to the terms and 86 | conditions of this Public License, which are limited to all Copyright and 87 | Similar Rights that apply to Your use of the Licensed Material and that the 88 | Licensor has authority to license. 89 | 90 | h. Licensor means the individual(s) or entity(ies) granting rights under this 91 | Public License. 92 | 93 | i. Share means to provide material to the public by any means or process that 94 | requires permission under the Licensed Rights, such as reproduction, public 95 | display, public performance, distribution, dissemination, communication, or 96 | importation, and to make material available to the public including in ways 97 | that members of the public may access the material from a place and at a time 98 | individually chosen by them. 99 | 100 | j. Sui Generis Database Rights means rights other than copyright resulting 101 | from Directive 96/9/EC of the European Parliament and of the Council of 11 102 | March 1996 on the legal protection of databases, as amended and/or succeeded, 103 | as well as other essentially equivalent rights anywhere in the world. 104 | 105 | k. You means the individual or entity exercising the Licensed Rights under 106 | this Public License. Your has a corresponding meaning. 107 | 108 | Section 2 – Scope. 109 | 110 | a. License grant. 111 | 112 | 1. Subject to the terms and conditions of this Public License, the Licensor 113 | hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, 114 | irrevocable license to exercise the Licensed Rights in the Licensed Material 115 | to: 116 | 117 | A. reproduce and Share the Licensed Material, in whole or in part; and 118 | 119 | B. produce, reproduce, and Share Adapted Material. 120 | 121 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions 122 | and Limitations apply to Your use, this Public License does not apply, and 123 | You do not need to comply with its terms and conditions. 124 | 125 | 3. Term. The term of this Public License is specified in Section 6(a). 126 | 127 | 4. Media and formats; technical modifications allowed. The Licensor authorizes 128 | You to exercise the Licensed Rights in all media and formats whether now known 129 | or hereafter created, and to make technical modifications necessary to do 130 | so. The Licensor waives and/or agrees not to assert any right or authority 131 | to forbid You from making technical modifications necessary to exercise the 132 | Licensed Rights, including technical modifications necessary to circumvent 133 | Effective Technological Measures. For purposes of this Public License, simply 134 | making modifications authorized by this Section 2(a)(4) never produces Adapted 135 | Material. 136 | 137 | 5. Downstream recipients. 138 | 139 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed 140 | Material automatically receives an offer from the Licensor to exercise the 141 | Licensed Rights under the terms and conditions of this Public License. 142 | 143 | B. No downstream restrictions. You may not offer or impose any additional 144 | or different terms or conditions on, or apply any Effective Technological 145 | Measures to, the Licensed Material if doing so restricts exercise of the Licensed 146 | Rights by any recipient of the Licensed Material. 147 | 148 | 6. No endorsement. Nothing in this Public License constitutes or may be construed 149 | as permission to assert or imply that You are, or that Your use of the Licensed 150 | Material is, connected with, or sponsored, endorsed, or granted official status 151 | by, the Licensor or others designated to receive attribution as provided in 152 | Section 3(a)(1)(A)(i). 153 | 154 | b. Other rights. 155 | 156 | 1. Moral rights, such as the right of integrity, are not licensed under this 157 | Public License, nor are publicity, privacy, and/or other similar personality 158 | rights; however, to the extent possible, the Licensor waives and/or agrees 159 | not to assert any such rights held by the Licensor to the limited extent necessary 160 | to allow You to exercise the Licensed Rights, but not otherwise. 161 | 162 | 2. Patent and trademark rights are not licensed under this Public License. 163 | 164 | 3. To the extent possible, the Licensor waives any right to collect royalties 165 | from You for the exercise of the Licensed Rights, whether directly or through 166 | a collecting society under any voluntary or waivable statutory or compulsory 167 | licensing scheme. In all other cases the Licensor expressly reserves any right 168 | to collect such royalties. 169 | 170 | Section 3 – License Conditions. 171 | 172 | Your exercise of the Licensed Rights is expressly made subject to the following 173 | conditions. 174 | 175 | a. Attribution. 176 | 177 | 1. If You Share the Licensed Material (including in modified form), You must: 178 | 179 | A. retain the following if it is supplied by the Licensor with the Licensed 180 | Material: 181 | 182 | i. identification of the creator(s) of the Licensed Material and any others 183 | designated to receive attribution, in any reasonable manner requested by the 184 | Licensor (including by pseudonym if designated); 185 | 186 | ii. a copyright notice; 187 | 188 | iii. a notice that refers to this Public License; 189 | 190 | iv. a notice that refers to the disclaimer of warranties; 191 | 192 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 193 | 194 | B. indicate if You modified the Licensed Material and retain an indication 195 | of any previous modifications; and 196 | 197 | C. indicate the Licensed Material is licensed under this Public License, and 198 | include the text of, or the URI or hyperlink to, this Public License. 199 | 200 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner 201 | based on the medium, means, and context in which You Share the Licensed Material. 202 | For example, it may be reasonable to satisfy the conditions by providing a 203 | URI or hyperlink to a resource that includes the required information. 204 | 205 | 3. If requested by the Licensor, You must remove any of the information required 206 | by Section 3(a)(1)(A) to the extent reasonably practicable. 207 | 208 | 4. If You Share Adapted Material You produce, the Adapter's License You apply 209 | must not prevent recipients of the Adapted Material from complying with this 210 | Public License. 211 | 212 | Section 4 – Sui Generis Database Rights. 213 | 214 | Where the Licensed Rights include Sui Generis Database Rights that apply to 215 | Your use of the Licensed Material: 216 | 217 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, 218 | reuse, reproduce, and Share all or a substantial portion of the contents of 219 | the database; 220 | 221 | b. if You include all or a substantial portion of the database contents in 222 | a database in which You have Sui Generis Database Rights, then the database 223 | in which You have Sui Generis Database Rights (but not its individual contents) 224 | is Adapted Material; and 225 | 226 | c. You must comply with the conditions in Section 3(a) if You Share all or 227 | a substantial portion of the contents of the database. 228 | 229 | For the avoidance of doubt, this Section 4 supplements and does not replace 230 | Your obligations under this Public License where the Licensed Rights include 231 | other Copyright and Similar Rights. 232 | 233 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 234 | 235 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, 236 | the Licensor offers the Licensed Material as-is and as-available, and makes 237 | no representations or warranties of any kind concerning the Licensed Material, 238 | whether express, implied, statutory, or other. This includes, without limitation, 239 | warranties of title, merchantability, fitness for a particular purpose, non-infringement, 240 | absence of latent or other defects, accuracy, or the presence or absence of 241 | errors, whether or not known or discoverable. Where disclaimers of warranties 242 | are not allowed in full or in part, this disclaimer may not apply to You. 243 | 244 | b. To the extent possible, in no event will the Licensor be liable to You 245 | on any legal theory (including, without limitation, negligence) or otherwise 246 | for any direct, special, indirect, incidental, consequential, punitive, exemplary, 247 | or other losses, costs, expenses, or damages arising out of this Public License 248 | or use of the Licensed Material, even if the Licensor has been advised of 249 | the possibility of such losses, costs, expenses, or damages. Where a limitation 250 | of liability is not allowed in full or in part, this limitation may not apply 251 | to You. 252 | 253 | c. The disclaimer of warranties and limitation of liability provided above 254 | shall be interpreted in a manner that, to the extent possible, most closely 255 | approximates an absolute disclaimer and waiver of all liability. 256 | 257 | Section 6 – Term and Termination. 258 | 259 | a. This Public License applies for the term of the Copyright and Similar Rights 260 | licensed here. However, if You fail to comply with this Public License, then 261 | Your rights under this Public License terminate automatically. 262 | 263 | b. Where Your right to use the Licensed Material has terminated under Section 264 | 6(a), it reinstates: 265 | 266 | 1. automatically as of the date the violation is cured, provided it is cured 267 | within 30 days of Your discovery of the violation; or 268 | 269 | 2. upon express reinstatement by the Licensor. 270 | 271 | c. For the avoidance of doubt, this Section 6(b) does not affect any right 272 | the Licensor may have to seek remedies for Your violations of this Public 273 | License. 274 | 275 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material 276 | under separate terms or conditions or stop distributing the Licensed Material 277 | at any time; however, doing so will not terminate this Public License. 278 | 279 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 280 | 281 | Section 7 – Other Terms and Conditions. 282 | 283 | a. The Licensor shall not be bound by any additional or different terms or 284 | conditions communicated by You unless expressly agreed. 285 | 286 | b. Any arrangements, understandings, or agreements regarding the Licensed 287 | Material not stated herein are separate from and independent of the terms 288 | and conditions of this Public License. 289 | 290 | Section 8 – Interpretation. 291 | 292 | a. For the avoidance of doubt, this Public License does not, and shall not 293 | be interpreted to, reduce, limit, restrict, or impose conditions on any use 294 | of the Licensed Material that could lawfully be made without permission under 295 | this Public License. 296 | 297 | b. To the extent possible, if any provision of this Public License is deemed 298 | unenforceable, it shall be automatically reformed to the minimum extent necessary 299 | to make it enforceable. If the provision cannot be reformed, it shall be severed 300 | from this Public License without affecting the enforceability of the remaining 301 | terms and conditions. 302 | 303 | c. No term or condition of this Public License will be waived and no failure 304 | to comply consented to unless expressly agreed to by the Licensor. 305 | 306 | d. Nothing in this Public License constitutes or may be interpreted as a limitation 307 | upon, or waiver of, any privileges and immunities that apply to the Licensor 308 | or You, including from the legal processes of any jurisdiction or authority. 309 | 310 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative 311 | Commons may elect to apply one of its public licenses to material it publishes 312 | and in those instances will be considered the "Licensor." The text of the 313 | Creative Commons public licenses is dedicated to the public domain under the 314 | CC0 Public Domain Dedication. Except for the limited purpose of indicating 315 | that material is shared under a Creative Commons public license or as otherwise 316 | permitted by the Creative Commons policies published at creativecommons.org/policies, 317 | Creative Commons does not authorize the use of the trademark "Creative Commons" 318 | or any other trademark or logo of Creative Commons without its prior written 319 | consent including, without limitation, in connection with any unauthorized 320 | modifications to any of its public licenses or any other arrangements, understandings, 321 | or agreements concerning use of licensed material. For the avoidance of doubt, 322 | this paragraph does not form part of the public licenses. 323 | 324 | Creative Commons may be contacted at creativecommons.org. 325 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSES/Unlicense.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute 4 | this software, either in source code form or as a compiled binary, for any 5 | purpose, commercial or non-commercial, and by any means. 6 | 7 | In jurisdictions that recognize copyright laws, the author or authors of this 8 | software dedicate any and all copyright interest in the software to the public 9 | domain. We make this dedication for the benefit of the public at large and 10 | to the detriment of our heirs and successors. We intend this dedication to 11 | be an overt act of relinquishment in perpetuity of all present and future 12 | rights to this software under copyright law. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 19 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, 20 | please refer to 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | Introduction 3 | ============ 4 | 5 | .. image:: https://readthedocs.org/projects/adafruit-circuitpython-hid/badge/?version=latest 6 | :target: https://docs.circuitpython.org/projects/hid/en/latest/ 7 | :alt: Documentation Status 8 | 9 | .. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg 10 | :target: https://adafru.it/discord 11 | :alt: Discord 12 | 13 | .. image:: https://github.com/adafruit/Adafruit_CircuitPython_HID/workflows/Build%20CI/badge.svg 14 | :target: https://github.com/adafruit/Adafruit_CircuitPython_HID/actions/ 15 | :alt: Build Status 16 | 17 | .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json 18 | :target: https://github.com/astral-sh/ruff 19 | :alt: Code Style: Ruff 20 | 21 | 22 | This driver simulates USB HID devices. Currently keyboard and mouse are implemented. 23 | 24 | Dependencies 25 | ============= 26 | This driver depends on: 27 | 28 | * `Adafruit CircuitPython `_ 29 | 30 | Please ensure all dependencies are available on the CircuitPython filesystem. 31 | This is easily achieved by downloading 32 | `the Adafruit library and driver bundle `_. 33 | 34 | Additional Layouts 35 | ==================== 36 | This library has an en-US layout. Please check out and expand `the library from Neradoc `_ for additional layouts. 37 | 38 | Usage Example 39 | ============= 40 | 41 | The ``Keyboard`` class sends keypress reports for a USB keyboard device to the host. 42 | 43 | The ``Keycode`` class defines USB HID keycodes to send using ``Keyboard``. 44 | 45 | .. code-block:: python 46 | 47 | import usb_hid 48 | from adafruit_hid.keyboard import Keyboard 49 | from adafruit_hid.keycode import Keycode 50 | 51 | # Set up a keyboard device. 52 | kbd = Keyboard(usb_hid.devices) 53 | 54 | # Type lowercase 'a'. Presses the 'a' key and releases it. 55 | kbd.send(Keycode.A) 56 | 57 | # Type capital 'A'. 58 | kbd.send(Keycode.SHIFT, Keycode.A) 59 | 60 | # Type control-x. 61 | kbd.send(Keycode.CONTROL, Keycode.X) 62 | 63 | # You can also control press and release actions separately. 64 | kbd.press(Keycode.CONTROL, Keycode.X) 65 | kbd.release_all() 66 | 67 | # Press and hold the shifted '1' key to get '!' (exclamation mark). 68 | kbd.press(Keycode.SHIFT, Keycode.ONE) 69 | # Release the ONE key and send another report. 70 | kbd.release(Keycode.ONE) 71 | # Press shifted '2' to get '@'. 72 | kbd.press(Keycode.TWO) 73 | # Release all keys. 74 | kbd.release_all() 75 | 76 | The ``KeyboardLayoutUS`` sends ASCII characters using keypresses. It assumes 77 | the host is set to accept keypresses from a US keyboard. 78 | 79 | If the host is expecting a non-US keyboard, the character to key mapping provided by 80 | ``KeyboardLayoutUS`` will not always be correct. 81 | Different keypresses will be needed in some cases. For instance, to type an ``'A'`` on 82 | a French keyboard (AZERTY instead of QWERTY), ``Keycode.Q`` should be pressed. 83 | 84 | Currently this package provides only ``KeyboardLayoutUS``. More ``KeyboardLayout`` 85 | classes could be added to handle non-US keyboards and the different input methods provided 86 | by various operating systems. 87 | 88 | .. code-block:: python 89 | 90 | import usb_hid 91 | from adafruit_hid.keyboard import Keyboard 92 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS 93 | 94 | kbd = Keyboard(usb_hid.devices) 95 | layout = KeyboardLayoutUS(kbd) 96 | 97 | # Type 'abc' followed by Enter (a newline). 98 | layout.write('abc\n') 99 | 100 | # Get the keycodes needed to type a '$'. 101 | # The method will return (Keycode.SHIFT, Keycode.FOUR). 102 | keycodes = layout.keycodes('$') 103 | 104 | The ``Mouse`` class simulates a three-button mouse with a scroll wheel. 105 | 106 | .. code-block:: python 107 | 108 | import usb_hid 109 | from adafruit_hid.mouse import Mouse 110 | 111 | m = Mouse(usb_hid.devices) 112 | 113 | # Click the left mouse button. 114 | m.click(Mouse.LEFT_BUTTON) 115 | 116 | # Move the mouse diagonally to the upper left. 117 | m.move(-100, -100, 0) 118 | 119 | # Roll the mouse wheel away from the user one unit. 120 | # Amount scrolled depends on the host. 121 | m.move(0, 0, -1) 122 | 123 | # Keyword arguments may also be used. Omitted arguments default to 0. 124 | m.move(x=-100, y=-100) 125 | m.move(wheel=-1) 126 | 127 | # Move the mouse while holding down the left button. (click-drag). 128 | m.press(Mouse.LEFT_BUTTON) 129 | m.move(x=50, y=20) 130 | m.release_all() # or m.release(Mouse.LEFT_BUTTON) 131 | 132 | The ``ConsumerControl`` class emulates consumer control devices such as 133 | remote controls, or the multimedia keys on certain keyboards. 134 | 135 | .. code-block:: python 136 | 137 | import usb_hid 138 | from adafruit_hid.consumer_control import ConsumerControl 139 | from adafruit_hid.consumer_control_code import ConsumerControlCode 140 | 141 | cc = ConsumerControl(usb_hid.devices) 142 | 143 | # Raise volume. 144 | cc.send(ConsumerControlCode.VOLUME_INCREMENT) 145 | 146 | # Pause or resume playback. 147 | cc.send(ConsumerControlCode.PLAY_PAUSE) 148 | 149 | Documentation 150 | ============= 151 | 152 | API documentation for this library can be found on `Read the Docs `_. 153 | 154 | For information on building library documentation, please check out `this guide `_. 155 | 156 | Contributing 157 | ============ 158 | 159 | Contributions are welcome! Please read our `Code of Conduct 160 | `_ 161 | before contributing to help this project stay welcoming. 162 | -------------------------------------------------------------------------------- /README.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /adafruit_hid/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid` 7 | ==================================================== 8 | 9 | This driver simulates USB HID devices. 10 | 11 | * Author(s): Scott Shawcroft, Dan Halbert 12 | """ 13 | 14 | # imports 15 | from __future__ import annotations 16 | 17 | import time 18 | 19 | try: 20 | import supervisor 21 | except ImportError: 22 | supervisor = None 23 | 24 | try: 25 | from typing import Sequence 26 | except ImportError: 27 | pass 28 | 29 | # usb_hid may not exist on some boards that still provide BLE or other HID devices. 30 | try: 31 | from usb_hid import Device 32 | except ImportError: 33 | Device = None 34 | 35 | __version__ = "0.0.0+auto.0" 36 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git" 37 | 38 | 39 | def find_device( 40 | devices: Sequence[object], 41 | *, 42 | usage_page: int, 43 | usage: int, 44 | timeout: int = None, 45 | ) -> object: 46 | """ 47 | Search through the provided sequence of devices to find the one with the matching 48 | usage_page and usage. 49 | 50 | :param timeout: Time in seconds to wait for USB to become ready before timing out. 51 | Defaults to None to wait indefinitely. 52 | Ignored if device is not a `usb_hid.Device`; it might be BLE, for instance. 53 | """ 54 | 55 | if hasattr(devices, "send_report"): 56 | devices = [devices] # type: ignore 57 | device = None 58 | for dev in devices: 59 | if dev.usage_page == usage_page and dev.usage == usage and hasattr(dev, "send_report"): 60 | device = dev 61 | break 62 | if device is None: 63 | raise ValueError("Could not find matching HID device.") 64 | 65 | # Wait for USB to be connected only if this is a usb_hid.Device. 66 | if Device and isinstance(device, Device): 67 | if supervisor is None: 68 | # Blinka doesn't have supervisor (see issue Adafruit_Blinka#711), so wait 69 | # one second for USB to become ready 70 | time.sleep(1.0) 71 | elif timeout is None: 72 | # default behavior: wait indefinitely for USB to become ready 73 | while not supervisor.runtime.usb_connected: 74 | time.sleep(1.0) 75 | else: 76 | # wait up to timeout seconds for USB to become ready 77 | for _ in range(timeout): 78 | if supervisor.runtime.usb_connected: 79 | return device 80 | time.sleep(1.0) 81 | raise OSError("Failed to initialize HID device. Is USB connected?") 82 | 83 | return device 84 | -------------------------------------------------------------------------------- /adafruit_hid/consumer_control.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.consumer_control.ConsumerControl` 7 | ==================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | import sys 13 | 14 | if sys.implementation.version[0] < 3: 15 | raise ImportError(f"{__name__} is not supported in CircuitPython 2.x or lower") 16 | 17 | import struct 18 | 19 | from . import find_device 20 | 21 | try: 22 | from typing import Sequence 23 | 24 | import usb_hid 25 | except ImportError: 26 | pass 27 | 28 | 29 | class ConsumerControl: 30 | """Send ConsumerControl code reports, used by multimedia keyboards, remote controls, etc.""" 31 | 32 | def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: 33 | """Create a ConsumerControl object that will send Consumer Control Device HID reports. 34 | 35 | :param timeout: Time in seconds to wait for USB to become ready before timing out. 36 | Defaults to None to wait indefinitely. 37 | 38 | Devices can be a sequence of devices that includes a Consumer Control device or a CC device 39 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 40 | ``usage``. 41 | """ 42 | self._consumer_device = find_device(devices, usage_page=0x0C, usage=0x01, timeout=timeout) 43 | 44 | # Reuse this bytearray to send consumer reports. 45 | self._report = bytearray(2) 46 | 47 | def send(self, consumer_code: int) -> None: 48 | """Send a report to do the specified consumer control action, 49 | and then stop the action (so it will not repeat). 50 | 51 | :param consumer_code: a 16-bit consumer control code. 52 | 53 | Examples:: 54 | 55 | from adafruit_hid.consumer_control_code import ConsumerControlCode 56 | 57 | # Raise volume. 58 | consumer_control.send(ConsumerControlCode.VOLUME_INCREMENT) 59 | 60 | # Advance to next track (song). 61 | consumer_control.send(ConsumerControlCode.SCAN_NEXT_TRACK) 62 | """ 63 | self.press(consumer_code) 64 | self.release() 65 | 66 | def press(self, consumer_code: int) -> None: 67 | """Send a report to indicate that the given key has been pressed. 68 | Only one consumer control action can be pressed at a time, so any one 69 | that was previously pressed will be released. 70 | 71 | :param consumer_code: a 16-bit consumer control code. 72 | 73 | Examples:: 74 | 75 | from adafruit_hid.consumer_control_code import ConsumerControlCode 76 | 77 | # Raise volume for 0.5 seconds 78 | consumer_control.press(ConsumerControlCode.VOLUME_INCREMENT) 79 | time.sleep(0.5) 80 | consumer_control.release() 81 | """ 82 | struct.pack_into(" None: 86 | """Send a report indicating that the consumer control key has been 87 | released. Only one consumer control key can be pressed at a time. 88 | 89 | Examples:: 90 | 91 | from adafruit_hid.consumer_control_code import ConsumerControlCode 92 | 93 | # Raise volume for 0.5 seconds 94 | consumer_control.press(ConsumerControlCode.VOLUME_INCREMENT) 95 | time.sleep(0.5) 96 | consumer_control.release() 97 | """ 98 | self._report[0] = self._report[1] = 0x0 99 | self._consumer_device.send_report(self._report) 100 | -------------------------------------------------------------------------------- /adafruit_hid/consumer_control_code.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.consumer_control_code.ConsumerControlCode` 7 | ======================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | 13 | class ConsumerControlCode: 14 | """USB HID Consumer Control Device constants. 15 | 16 | This list includes a few common consumer control codes from 17 | https://www.usb.org/sites/default/files/hut1_21_0.pdf#page=118. 18 | """ 19 | 20 | RECORD = 0xB2 21 | """Record""" 22 | FAST_FORWARD = 0xB3 23 | """Fast Forward""" 24 | REWIND = 0xB4 25 | """Rewind""" 26 | SCAN_NEXT_TRACK = 0xB5 27 | """Skip to next track""" 28 | SCAN_PREVIOUS_TRACK = 0xB6 29 | """Go back to previous track""" 30 | STOP = 0xB7 31 | """Stop""" 32 | EJECT = 0xB8 33 | """Eject""" 34 | PLAY_PAUSE = 0xCD 35 | """Play/Pause toggle""" 36 | MUTE = 0xE2 37 | """Mute""" 38 | VOLUME_DECREMENT = 0xEA 39 | """Decrease volume""" 40 | VOLUME_INCREMENT = 0xE9 41 | """Increase volume""" 42 | BRIGHTNESS_DECREMENT = 0x70 43 | """Decrease Brightness""" 44 | BRIGHTNESS_INCREMENT = 0x6F 45 | """Increase Brightness""" 46 | -------------------------------------------------------------------------------- /adafruit_hid/keyboard.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.keyboard.Keyboard` 7 | ==================================================== 8 | 9 | * Author(s): Scott Shawcroft, Dan Halbert 10 | """ 11 | 12 | from micropython import const 13 | 14 | from . import find_device 15 | from .keycode import Keycode 16 | 17 | try: 18 | from typing import Sequence 19 | 20 | import usb_hid 21 | except ImportError: 22 | pass 23 | 24 | _MAX_KEYPRESSES = const(6) 25 | 26 | 27 | class Keyboard: 28 | """Send HID keyboard reports.""" 29 | 30 | LED_NUM_LOCK = 0x01 31 | """LED Usage ID for Num Lock""" 32 | LED_CAPS_LOCK = 0x02 33 | """LED Usage ID for Caps Lock""" 34 | LED_SCROLL_LOCK = 0x04 35 | """LED Usage ID for Scroll Lock""" 36 | LED_COMPOSE = 0x08 37 | """LED Usage ID for Compose""" 38 | 39 | # No more than _MAX_KEYPRESSES regular keys may be pressed at once. 40 | 41 | def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: 42 | """Create a Keyboard object that will send keyboard HID reports. 43 | 44 | :param timeout: Time in seconds to wait for USB to become ready before timing out. 45 | Defaults to None to wait indefinitely. 46 | 47 | Devices can be a sequence of devices that includes a keyboard device or a keyboard device 48 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 49 | ``usage``. 50 | """ 51 | self._keyboard_device = find_device(devices, usage_page=0x1, usage=0x06, timeout=timeout) 52 | 53 | # Reuse this bytearray to send keyboard reports. 54 | self.report = bytearray(8) 55 | 56 | # report[0] modifiers 57 | # report[1] unused 58 | # report[2:8] regular key presses 59 | 60 | # View onto byte 0 in report. 61 | self.report_modifier = memoryview(self.report)[0:1] 62 | 63 | # List of regular keys currently pressed. 64 | # View onto bytes 2-7 in report. 65 | self.report_keys = memoryview(self.report)[2:] 66 | 67 | # No keyboard LEDs on. 68 | self._led_status = b"\x00" 69 | 70 | def press(self, *keycodes: int) -> None: 71 | """Send a report indicating that the given keys have been pressed. 72 | 73 | :param keycodes: Press these keycodes all at once. 74 | :raises ValueError: if more than six regular keys are pressed. 75 | 76 | Keycodes may be modifiers or regular keys. 77 | No more than six regular keys may be pressed simultaneously. 78 | 79 | Examples:: 80 | 81 | from adafruit_hid.keycode import Keycode 82 | 83 | # Press ctrl-x. 84 | kbd.press(Keycode.LEFT_CONTROL, Keycode.X) 85 | 86 | # Or, more conveniently, use the CONTROL alias for LEFT_CONTROL: 87 | kbd.press(Keycode.CONTROL, Keycode.X) 88 | 89 | # Press a, b, c keys all at once. 90 | kbd.press(Keycode.A, Keycode.B, Keycode.C) 91 | """ 92 | for keycode in keycodes: 93 | self._add_keycode_to_report(keycode) 94 | self._keyboard_device.send_report(self.report) 95 | 96 | def release(self, *keycodes: int) -> None: 97 | """Send a USB HID report indicating that the given keys have been released. 98 | 99 | :param keycodes: Release these keycodes all at once. 100 | 101 | If a keycode to be released was not pressed, it is ignored. 102 | 103 | Example:: 104 | 105 | # release SHIFT key 106 | kbd.release(Keycode.SHIFT) 107 | """ 108 | for keycode in keycodes: 109 | self._remove_keycode_from_report(keycode) 110 | self._keyboard_device.send_report(self.report) 111 | 112 | def release_all(self) -> None: 113 | """Release all pressed keys.""" 114 | for i in range(8): 115 | self.report[i] = 0 116 | self._keyboard_device.send_report(self.report) 117 | 118 | def send(self, *keycodes: int) -> None: 119 | """Press the given keycodes and then release all pressed keys. 120 | 121 | :param keycodes: keycodes to send together 122 | """ 123 | self.press(*keycodes) 124 | self.release_all() 125 | 126 | def _add_keycode_to_report(self, keycode: int) -> None: 127 | """Add a single keycode to the USB HID report.""" 128 | modifier = Keycode.modifier_bit(keycode) 129 | if modifier: 130 | # Set bit for this modifier. 131 | self.report_modifier[0] |= modifier 132 | else: 133 | report_keys = self.report_keys 134 | # Don't press twice. 135 | for i in range(_MAX_KEYPRESSES): 136 | report_key = report_keys[i] 137 | if report_key == 0: 138 | # Put keycode in first empty slot. Since the report_keys 139 | # are compact and unique, this is not a repeated key 140 | report_keys[i] = keycode 141 | return 142 | if report_key == keycode: 143 | # Already pressed. 144 | return 145 | # All slots are filled. Shuffle down and reuse last slot 146 | for i in range(_MAX_KEYPRESSES - 1): 147 | report_keys[i] = report_keys[i + 1] 148 | report_keys[-1] = keycode 149 | 150 | def _remove_keycode_from_report(self, keycode: int) -> None: 151 | """Remove a single keycode from the report.""" 152 | modifier = Keycode.modifier_bit(keycode) 153 | if modifier: 154 | # Turn off the bit for this modifier. 155 | self.report_modifier[0] &= ~modifier 156 | else: 157 | report_keys = self.report_keys 158 | # Clear the at most one matching slot and move remaining keys down 159 | j = 0 160 | for i in range(_MAX_KEYPRESSES): 161 | pressed = report_keys[i] 162 | if not pressed: 163 | break # Handled all used report slots 164 | if pressed == keycode: 165 | continue # Remove this entry 166 | if i != j: 167 | report_keys[j] = report_keys[i] 168 | j += 1 169 | # Clear any remaining slots 170 | while j < _MAX_KEYPRESSES and report_keys[j]: 171 | report_keys[j] = 0 172 | j += 1 173 | 174 | @property 175 | def led_status(self) -> bytes: 176 | """Returns the last received report""" 177 | # get_last_received_report() returns None when nothing was received 178 | led_report = self._keyboard_device.get_last_received_report() 179 | if led_report is not None: 180 | self._led_status = led_report 181 | return self._led_status 182 | 183 | def led_on(self, led_code: int) -> bool: 184 | """Returns whether an LED is on based on the led code 185 | 186 | Examples:: 187 | 188 | import usb_hid 189 | from adafruit_hid.keyboard import Keyboard 190 | from adafruit_hid.keycode import Keycode 191 | import time 192 | 193 | # Initialize Keyboard 194 | kbd = Keyboard(usb_hid.devices) 195 | 196 | # Press and release CapsLock. 197 | kbd.press(Keycode.CAPS_LOCK) 198 | time.sleep(.09) 199 | kbd.release(Keycode.CAPS_LOCK) 200 | 201 | # Check status of the LED_CAPS_LOCK 202 | print(kbd.led_on(Keyboard.LED_CAPS_LOCK)) 203 | 204 | """ 205 | return bool(self.led_status[0] & led_code) 206 | -------------------------------------------------------------------------------- /adafruit_hid/keyboard_layout_base.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.keyboard_layout_base.KeyboardLayoutBase` 7 | ======================================================= 8 | 9 | * Author(s): Dan Halbert, AngainorDev, Neradoc 10 | """ 11 | 12 | try: 13 | from typing import Tuple 14 | 15 | from .keyboard import Keyboard 16 | except ImportError: 17 | pass 18 | 19 | from time import sleep 20 | 21 | __version__ = "0.0.0+auto.0" 22 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HID.git" 23 | 24 | 25 | class KeyboardLayoutBase: 26 | """Base class for keyboard layouts. Uses the tables defined in the subclass 27 | to map UTF-8 characters to appropriate keypresses. 28 | 29 | Non-supported characters and most control characters will raise an exception. 30 | """ 31 | 32 | SHIFT_FLAG = 0x80 33 | """Bit set in any keycode byte if the shift key is required for the character.""" 34 | ALTGR_FLAG = 0x80 35 | """Bit set in the combined keys table if altgr is required for the first key.""" 36 | SHIFT_CODE = 0xE1 37 | """The SHIFT keycode, to avoid dependency to the Keycode class.""" 38 | RIGHT_ALT_CODE = 0xE6 39 | """The ALTGR keycode, to avoid dependency to the Keycode class.""" 40 | ASCII_TO_KEYCODE = () 41 | """Bytes string of keycodes for low ASCII characters, indexed by the ASCII value. 42 | Keycodes use the `SHIFT_FLAG` if needed. 43 | Dead keys are excluded by assigning the keycode 0.""" 44 | HIGHER_ASCII = {} 45 | """Dictionary that associates the ord() int value of high ascii and utf8 characters 46 | to their keycode. Keycodes use the `SHIFT_FLAG` if needed.""" 47 | NEED_ALTGR = "" 48 | """Characters in `ASCII_TO_KEYCODE` and `HIGHER_ASCII` that need 49 | the ALTGR key pressed to type.""" 50 | COMBINED_KEYS = {} 51 | """ 52 | Dictionary of characters (indexed by ord() value) that can be accessed by typing first 53 | a dead key followed by a regular key, like ``ñ`` as ``~ + n``. The value is a 2-bytes int: 54 | the high byte is the dead-key keycode (including SHIFT_FLAG), the low byte is the ascii code 55 | of the second character, with ALTGR_FLAG set if the dead key (the first key) needs ALTGR. 56 | 57 | The combined-key codes bits are: ``0b SDDD DDDD AKKK KKKK``: 58 | ``S`` is the shift flag for the **first** key, 59 | ``DDD DDDD`` is the keycode for the **first** key, 60 | ``A`` is the altgr flag for the **first** key, 61 | ``KKK KKKK`` is the (low) ASCII code for the second character. 62 | """ 63 | 64 | def __init__(self, keyboard: Keyboard) -> None: 65 | """Specify the layout for the given keyboard. 66 | 67 | :param keyboard: a Keyboard object. Write characters to this keyboard when requested. 68 | 69 | Example:: 70 | 71 | kbd = Keyboard(usb_hid.devices) 72 | layout = KeyboardLayout(kbd) 73 | """ 74 | self.keyboard = keyboard 75 | 76 | def _write(self, keycode: int, altgr: bool = False) -> None: 77 | """Type a key combination based on shift bit and altgr bool 78 | 79 | :param keycode: int value of the keycode, with the shift bit. 80 | :param altgr: bool indicating if the altgr key should be pressed too. 81 | """ 82 | # Add altgr modifier if needed 83 | if altgr: 84 | self.keyboard.press(self.RIGHT_ALT_CODE) 85 | # If this is a shifted char, clear the SHIFT flag and press the SHIFT key. 86 | if keycode & self.SHIFT_FLAG: 87 | keycode &= ~self.SHIFT_FLAG 88 | self.keyboard.press(self.SHIFT_CODE) 89 | self.keyboard.press(keycode) 90 | self.keyboard.release_all() 91 | 92 | def write(self, string: str, delay: float = None) -> None: 93 | """Type the string by pressing and releasing keys on my keyboard. 94 | 95 | :param string: A string of UTF-8 characters to convert to key presses and send. 96 | :param float delay: Optional delay in seconds between key presses. 97 | :raises ValueError: if any of the characters has no keycode 98 | (such as some control characters). 99 | 100 | Example:: 101 | 102 | # Write abc followed by Enter to the keyboard 103 | layout.write('abc\\n') 104 | """ 105 | for char in string: 106 | # find easy ones first 107 | keycode = self._char_to_keycode(char) 108 | if keycode > 0: 109 | self._write(keycode, char in self.NEED_ALTGR) 110 | # find combined keys 111 | elif ord(char) in self.COMBINED_KEYS: 112 | # first key (including shift bit) 113 | cchar = self.COMBINED_KEYS[ord(char)] 114 | self._write(cchar >> 8, cchar & self.ALTGR_FLAG) 115 | # second key (removing the altgr bit) 116 | char = chr(cchar & 0xFF & (~self.ALTGR_FLAG)) 117 | keycode = self._char_to_keycode(char) 118 | # assume no altgr needed for second key 119 | self._write(keycode, False) 120 | else: 121 | raise ValueError( 122 | "No keycode available for character {letter} ({num}/0x{num:02x}).".format( 123 | letter=repr(char), num=ord(char) 124 | ) 125 | ) 126 | 127 | if delay is not None: 128 | sleep(delay) 129 | 130 | def keycodes(self, char: str) -> Tuple[int, ...]: 131 | """Return a tuple of keycodes needed to type the given character. 132 | 133 | :param char: A single UTF8 character in a string. 134 | :type char: str of length one. 135 | :returns: tuple of Keycode keycodes. 136 | :raises ValueError: if there is no keycode for ``char``. 137 | 138 | Examples:: 139 | 140 | # Returns (Keycode.TAB,) 141 | keycodes('\t') 142 | # Returns (Keycode.A,) 143 | keycode('a') 144 | # Returns (Keycode.SHIFT, Keycode.A) 145 | keycode('A') 146 | # Raises ValueError with a US layout because it's an unknown character 147 | keycode('é') 148 | """ 149 | keycode = self._char_to_keycode(char) 150 | if keycode == 0: 151 | raise ValueError( 152 | "No keycode available for character {letter} ({num}/0x{num:02x}).".format( 153 | letter=repr(char), num=ord(char) 154 | ) 155 | ) 156 | 157 | codes = [] 158 | if char in self.NEED_ALTGR: 159 | codes.append(self.RIGHT_ALT_CODE) 160 | if keycode & self.SHIFT_FLAG: 161 | codes.extend((self.SHIFT_CODE, keycode & ~self.SHIFT_FLAG)) 162 | else: 163 | codes.append(keycode) 164 | 165 | return codes 166 | 167 | def _above128char_to_keycode(self, char: str) -> int: 168 | """Return keycode for above 128 utf8 codes. 169 | 170 | A character can be indexed by the char itself or its int ord() value. 171 | 172 | :param char_val: char value 173 | :return: keycode, with modifiers if needed 174 | """ 175 | if ord(char) in self.HIGHER_ASCII: 176 | return self.HIGHER_ASCII[ord(char)] 177 | if char in self.HIGHER_ASCII: 178 | return self.HIGHER_ASCII[char] 179 | return 0 180 | 181 | def _char_to_keycode(self, char: str) -> int: 182 | """Return the HID keycode for the given character, with the SHIFT_FLAG possibly set. 183 | 184 | If the character requires pressing the Shift key, the SHIFT_FLAG bit is set. 185 | You must clear this bit before passing the keycode in a USB report. 186 | """ 187 | char_val = ord(char) 188 | if char_val > len(self.ASCII_TO_KEYCODE): 189 | return self._above128char_to_keycode(char) 190 | keycode = self.ASCII_TO_KEYCODE[char_val] 191 | return keycode 192 | -------------------------------------------------------------------------------- /adafruit_hid/keyboard_layout_us.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.keyboard_layout_us.KeyboardLayoutUS` 7 | ======================================================= 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | from .keyboard_layout_base import KeyboardLayoutBase 13 | 14 | 15 | class KeyboardLayoutUS(KeyboardLayoutBase): 16 | """Map ASCII characters to appropriate keypresses on a standard US PC keyboard. 17 | 18 | Non-ASCII characters and most control characters will raise an exception. 19 | """ 20 | 21 | # The ASCII_TO_KEYCODE bytes object is used as a table to maps ASCII 0-127 22 | # to the corresponding # keycode on a US 104-key keyboard. 23 | # The user should not normally need to use this table, 24 | # but it is not marked as private. 25 | # 26 | # Because the table only goes to 127, we use the top bit of each byte (ox80) to indicate 27 | # that the shift key should be pressed. So any values 0x{8,9,a,b}* are shifted characters. 28 | # 29 | # The Python compiler will concatenate all these bytes literals into a single bytes object. 30 | # Micropython/CircuitPython will store the resulting bytes constant in flash memory 31 | # if it's in a .mpy file, so it doesn't use up valuable RAM. 32 | # 33 | # \x00 entries have no keyboard key and so won't be sent. 34 | ASCII_TO_KEYCODE = ( 35 | b"\x00" # NUL 36 | b"\x00" # SOH 37 | b"\x00" # STX 38 | b"\x00" # ETX 39 | b"\x00" # EOT 40 | b"\x00" # ENQ 41 | b"\x00" # ACK 42 | b"\x00" # BEL \a 43 | b"\x2a" # BS BACKSPACE \b (called DELETE in the usb.org document) 44 | b"\x2b" # TAB \t 45 | b"\x28" # LF \n (called Return or ENTER in the usb.org document) 46 | b"\x00" # VT \v 47 | b"\x00" # FF \f 48 | b"\x00" # CR \r 49 | b"\x00" # SO 50 | b"\x00" # SI 51 | b"\x00" # DLE 52 | b"\x00" # DC1 53 | b"\x00" # DC2 54 | b"\x00" # DC3 55 | b"\x00" # DC4 56 | b"\x00" # NAK 57 | b"\x00" # SYN 58 | b"\x00" # ETB 59 | b"\x00" # CAN 60 | b"\x00" # EM 61 | b"\x00" # SUB 62 | b"\x29" # ESC 63 | b"\x00" # FS 64 | b"\x00" # GS 65 | b"\x00" # RS 66 | b"\x00" # US 67 | b"\x2c" # SPACE 68 | b"\x9e" # ! x1e|SHIFT_FLAG (shift 1) 69 | b"\xb4" # " x34|SHIFT_FLAG (shift ') 70 | b"\xa0" # # x20|SHIFT_FLAG (shift 3) 71 | b"\xa1" # $ x21|SHIFT_FLAG (shift 4) 72 | b"\xa2" # % x22|SHIFT_FLAG (shift 5) 73 | b"\xa4" # & x24|SHIFT_FLAG (shift 7) 74 | b"\x34" # ' 75 | b"\xa6" # ( x26|SHIFT_FLAG (shift 9) 76 | b"\xa7" # ) x27|SHIFT_FLAG (shift 0) 77 | b"\xa5" # * x25|SHIFT_FLAG (shift 8) 78 | b"\xae" # + x2e|SHIFT_FLAG (shift =) 79 | b"\x36" # , 80 | b"\x2d" # - 81 | b"\x37" # . 82 | b"\x38" # / 83 | b"\x27" # 0 84 | b"\x1e" # 1 85 | b"\x1f" # 2 86 | b"\x20" # 3 87 | b"\x21" # 4 88 | b"\x22" # 5 89 | b"\x23" # 6 90 | b"\x24" # 7 91 | b"\x25" # 8 92 | b"\x26" # 9 93 | b"\xb3" # : x33|SHIFT_FLAG (shift ;) 94 | b"\x33" # ; 95 | b"\xb6" # < x36|SHIFT_FLAG (shift ,) 96 | b"\x2e" # = 97 | b"\xb7" # > x37|SHIFT_FLAG (shift .) 98 | b"\xb8" # ? x38|SHIFT_FLAG (shift /) 99 | b"\x9f" # @ x1f|SHIFT_FLAG (shift 2) 100 | b"\x84" # A x04|SHIFT_FLAG (shift a) 101 | b"\x85" # B x05|SHIFT_FLAG (etc.) 102 | b"\x86" # C x06|SHIFT_FLAG 103 | b"\x87" # D x07|SHIFT_FLAG 104 | b"\x88" # E x08|SHIFT_FLAG 105 | b"\x89" # F x09|SHIFT_FLAG 106 | b"\x8a" # G x0a|SHIFT_FLAG 107 | b"\x8b" # H x0b|SHIFT_FLAG 108 | b"\x8c" # I x0c|SHIFT_FLAG 109 | b"\x8d" # J x0d|SHIFT_FLAG 110 | b"\x8e" # K x0e|SHIFT_FLAG 111 | b"\x8f" # L x0f|SHIFT_FLAG 112 | b"\x90" # M x10|SHIFT_FLAG 113 | b"\x91" # N x11|SHIFT_FLAG 114 | b"\x92" # O x12|SHIFT_FLAG 115 | b"\x93" # P x13|SHIFT_FLAG 116 | b"\x94" # Q x14|SHIFT_FLAG 117 | b"\x95" # R x15|SHIFT_FLAG 118 | b"\x96" # S x16|SHIFT_FLAG 119 | b"\x97" # T x17|SHIFT_FLAG 120 | b"\x98" # U x18|SHIFT_FLAG 121 | b"\x99" # V x19|SHIFT_FLAG 122 | b"\x9a" # W x1a|SHIFT_FLAG 123 | b"\x9b" # X x1b|SHIFT_FLAG 124 | b"\x9c" # Y x1c|SHIFT_FLAG 125 | b"\x9d" # Z x1d|SHIFT_FLAG 126 | b"\x2f" # [ 127 | b"\x31" # \ backslash 128 | b"\x30" # ] 129 | b"\xa3" # ^ x23|SHIFT_FLAG (shift 6) 130 | b"\xad" # _ x2d|SHIFT_FLAG (shift -) 131 | b"\x35" # ` 132 | b"\x04" # a 133 | b"\x05" # b 134 | b"\x06" # c 135 | b"\x07" # d 136 | b"\x08" # e 137 | b"\x09" # f 138 | b"\x0a" # g 139 | b"\x0b" # h 140 | b"\x0c" # i 141 | b"\x0d" # j 142 | b"\x0e" # k 143 | b"\x0f" # l 144 | b"\x10" # m 145 | b"\x11" # n 146 | b"\x12" # o 147 | b"\x13" # p 148 | b"\x14" # q 149 | b"\x15" # r 150 | b"\x16" # s 151 | b"\x17" # t 152 | b"\x18" # u 153 | b"\x19" # v 154 | b"\x1a" # w 155 | b"\x1b" # x 156 | b"\x1c" # y 157 | b"\x1d" # z 158 | b"\xaf" # { x2f|SHIFT_FLAG (shift [) 159 | b"\xb1" # | x31|SHIFT_FLAG (shift \) 160 | b"\xb0" # } x30|SHIFT_FLAG (shift ]) 161 | b"\xb5" # ~ x35|SHIFT_FLAG (shift `) 162 | b"\x4c" # DEL DELETE (called Forward Delete in usb.org document) 163 | ) 164 | 165 | 166 | KeyboardLayout = KeyboardLayoutUS 167 | -------------------------------------------------------------------------------- /adafruit_hid/keycode.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.keycode.Keycode` 7 | ==================================================== 8 | 9 | * Author(s): Scott Shawcroft, Dan Halbert 10 | """ 11 | 12 | 13 | class Keycode: 14 | """USB HID Keycode constants. 15 | 16 | This list is modeled after the names for USB keycodes defined in 17 | https://usb.org/sites/default/files/hut1_21_0.pdf#page=83. 18 | This list does not include every single code, but does include all the keys on 19 | a regular PC or Mac keyboard. 20 | 21 | Remember that keycodes are the names for key *positions* on a US keyboard, and may 22 | not correspond to the character that you mean to send if you want to emulate non-US keyboard. 23 | For instance, on a French keyboard (AZERTY instead of QWERTY), 24 | the keycode for 'q' is used to indicate an 'a'. Likewise, 'y' represents 'z' on 25 | a German keyboard. This is historical: the idea was that the keycaps could be changed 26 | without changing the keycodes sent, so that different firmware was not needed for 27 | different variations of a keyboard. 28 | """ 29 | 30 | A = 0x04 31 | """``a`` and ``A``""" 32 | B = 0x05 33 | """``b`` and ``B``""" 34 | C = 0x06 35 | """``c`` and ``C``""" 36 | D = 0x07 37 | """``d`` and ``D``""" 38 | E = 0x08 39 | """``e`` and ``E``""" 40 | F = 0x09 41 | """``f`` and ``F``""" 42 | G = 0x0A 43 | """``g`` and ``G``""" 44 | H = 0x0B 45 | """``h`` and ``H``""" 46 | I = 0x0C 47 | """``i`` and ``I``""" 48 | J = 0x0D 49 | """``j`` and ``J``""" 50 | K = 0x0E 51 | """``k`` and ``K``""" 52 | L = 0x0F 53 | """``l`` and ``L``""" 54 | M = 0x10 55 | """``m`` and ``M``""" 56 | N = 0x11 57 | """``n`` and ``N``""" 58 | O = 0x12 59 | """``o`` and ``O``""" 60 | P = 0x13 61 | """``p`` and ``P``""" 62 | Q = 0x14 63 | """``q`` and ``Q``""" 64 | R = 0x15 65 | """``r`` and ``R``""" 66 | S = 0x16 67 | """``s`` and ``S``""" 68 | T = 0x17 69 | """``t`` and ``T``""" 70 | U = 0x18 71 | """``u`` and ``U``""" 72 | V = 0x19 73 | """``v`` and ``V``""" 74 | W = 0x1A 75 | """``w`` and ``W``""" 76 | X = 0x1B 77 | """``x`` and ``X``""" 78 | Y = 0x1C 79 | """``y`` and ``Y``""" 80 | Z = 0x1D 81 | """``z`` and ``Z``""" 82 | 83 | ONE = 0x1E 84 | """``1`` and ``!``""" 85 | TWO = 0x1F 86 | """``2`` and ``@``""" 87 | THREE = 0x20 88 | """``3`` and ``#``""" 89 | FOUR = 0x21 90 | """``4`` and ``$``""" 91 | FIVE = 0x22 92 | """``5`` and ``%``""" 93 | SIX = 0x23 94 | """``6`` and ``^``""" 95 | SEVEN = 0x24 96 | """``7`` and ``&``""" 97 | EIGHT = 0x25 98 | """``8`` and ``*``""" 99 | NINE = 0x26 100 | """``9`` and ``(``""" 101 | ZERO = 0x27 102 | """``0`` and ``)``""" 103 | ENTER = 0x28 104 | """Enter (Return)""" 105 | RETURN = ENTER 106 | """Alias for ``ENTER``""" 107 | ESCAPE = 0x29 108 | """Escape""" 109 | BACKSPACE = 0x2A 110 | """Delete backward (Backspace)""" 111 | TAB = 0x2B 112 | """Tab and Backtab""" 113 | SPACEBAR = 0x2C 114 | """Spacebar""" 115 | SPACE = SPACEBAR 116 | """Alias for SPACEBAR""" 117 | MINUS = 0x2D 118 | """``-` and ``_``""" 119 | EQUALS = 0x2E 120 | """``=` and ``+``""" 121 | LEFT_BRACKET = 0x2F 122 | """``[`` and ``{``""" 123 | RIGHT_BRACKET = 0x30 124 | """``]`` and ``}``""" 125 | BACKSLASH = 0x31 126 | r"""``\`` and ``|``""" 127 | POUND = 0x32 128 | """``#`` and ``~`` (Non-US keyboard)""" 129 | SEMICOLON = 0x33 130 | """``;`` and ``:``""" 131 | QUOTE = 0x34 132 | """``'`` and ``"``""" 133 | GRAVE_ACCENT = 0x35 134 | r""":literal:`\`` and ``~``""" 135 | COMMA = 0x36 136 | """``,`` and ``<``""" 137 | PERIOD = 0x37 138 | """``.`` and ``>``""" 139 | FORWARD_SLASH = 0x38 140 | """``/`` and ``?``""" 141 | 142 | CAPS_LOCK = 0x39 143 | """Caps Lock""" 144 | 145 | F1 = 0x3A 146 | """Function key F1""" 147 | F2 = 0x3B 148 | """Function key F2""" 149 | F3 = 0x3C 150 | """Function key F3""" 151 | F4 = 0x3D 152 | """Function key F4""" 153 | F5 = 0x3E 154 | """Function key F5""" 155 | F6 = 0x3F 156 | """Function key F6""" 157 | F7 = 0x40 158 | """Function key F7""" 159 | F8 = 0x41 160 | """Function key F8""" 161 | F9 = 0x42 162 | """Function key F9""" 163 | F10 = 0x43 164 | """Function key F10""" 165 | F11 = 0x44 166 | """Function key F11""" 167 | F12 = 0x45 168 | """Function key F12""" 169 | 170 | PRINT_SCREEN = 0x46 171 | """Print Screen (SysRq)""" 172 | SCROLL_LOCK = 0x47 173 | """Scroll Lock""" 174 | PAUSE = 0x48 175 | """Pause (Break)""" 176 | 177 | INSERT = 0x49 178 | """Insert""" 179 | HOME = 0x4A 180 | """Home (often moves to beginning of line)""" 181 | PAGE_UP = 0x4B 182 | """Go back one page""" 183 | DELETE = 0x4C 184 | """Delete forward""" 185 | END = 0x4D 186 | """End (often moves to end of line)""" 187 | PAGE_DOWN = 0x4E 188 | """Go forward one page""" 189 | 190 | RIGHT_ARROW = 0x4F 191 | """Move the cursor right""" 192 | LEFT_ARROW = 0x50 193 | """Move the cursor left""" 194 | DOWN_ARROW = 0x51 195 | """Move the cursor down""" 196 | UP_ARROW = 0x52 197 | """Move the cursor up""" 198 | 199 | KEYPAD_NUMLOCK = 0x53 200 | """Num Lock (Clear on Mac)""" 201 | KEYPAD_FORWARD_SLASH = 0x54 202 | """Keypad ``/``""" 203 | KEYPAD_ASTERISK = 0x55 204 | """Keypad ``*``""" 205 | KEYPAD_MINUS = 0x56 206 | """Keyapd ``-``""" 207 | KEYPAD_PLUS = 0x57 208 | """Keypad ``+``""" 209 | KEYPAD_ENTER = 0x58 210 | """Keypad Enter""" 211 | KEYPAD_ONE = 0x59 212 | """Keypad ``1`` and End""" 213 | KEYPAD_TWO = 0x5A 214 | """Keypad ``2`` and Down Arrow""" 215 | KEYPAD_THREE = 0x5B 216 | """Keypad ``3`` and PgDn""" 217 | KEYPAD_FOUR = 0x5C 218 | """Keypad ``4`` and Left Arrow""" 219 | KEYPAD_FIVE = 0x5D 220 | """Keypad ``5``""" 221 | KEYPAD_SIX = 0x5E 222 | """Keypad ``6`` and Right Arrow""" 223 | KEYPAD_SEVEN = 0x5F 224 | """Keypad ``7`` and Home""" 225 | KEYPAD_EIGHT = 0x60 226 | """Keypad ``8`` and Up Arrow""" 227 | KEYPAD_NINE = 0x61 228 | """Keypad ``9`` and PgUp""" 229 | KEYPAD_ZERO = 0x62 230 | """Keypad ``0`` and Ins""" 231 | KEYPAD_PERIOD = 0x63 232 | """Keypad ``.`` and Del""" 233 | KEYPAD_BACKSLASH = 0x64 234 | """Keypad ``\\`` and ``|`` (Non-US)""" 235 | 236 | APPLICATION = 0x65 237 | """Application: also known as the Menu key (Windows)""" 238 | POWER = 0x66 239 | """Power (Mac)""" 240 | KEYPAD_EQUALS = 0x67 241 | """Keypad ``=`` (Mac)""" 242 | F13 = 0x68 243 | """Function key F13 (Mac)""" 244 | F14 = 0x69 245 | """Function key F14 (Mac)""" 246 | F15 = 0x6A 247 | """Function key F15 (Mac)""" 248 | F16 = 0x6B 249 | """Function key F16 (Mac)""" 250 | F17 = 0x6C 251 | """Function key F17 (Mac)""" 252 | F18 = 0x6D 253 | """Function key F18 (Mac)""" 254 | F19 = 0x6E 255 | """Function key F19 (Mac)""" 256 | 257 | F20 = 0x6F 258 | """Function key F20""" 259 | F21 = 0x70 260 | """Function key F21""" 261 | F22 = 0x71 262 | """Function key F22""" 263 | F23 = 0x72 264 | """Function key F23""" 265 | F24 = 0x73 266 | """Function key F24""" 267 | 268 | LEFT_CONTROL = 0xE0 269 | """Control modifier left of the spacebar""" 270 | CONTROL = LEFT_CONTROL 271 | """Alias for LEFT_CONTROL""" 272 | LEFT_SHIFT = 0xE1 273 | """Shift modifier left of the spacebar""" 274 | SHIFT = LEFT_SHIFT 275 | """Alias for LEFT_SHIFT""" 276 | LEFT_ALT = 0xE2 277 | """Alt modifier left of the spacebar""" 278 | ALT = LEFT_ALT 279 | """Alias for LEFT_ALT; Alt is also known as Option (Mac)""" 280 | OPTION = ALT 281 | """Labeled as Option on some Mac keyboards""" 282 | LEFT_GUI = 0xE3 283 | """GUI modifier left of the spacebar""" 284 | GUI = LEFT_GUI 285 | """Alias for LEFT_GUI; GUI is also known as the Windows key, Command (Mac), or Meta""" 286 | WINDOWS = GUI 287 | """Labeled with a Windows logo on Windows keyboards""" 288 | COMMAND = GUI 289 | """Labeled as Command on Mac keyboards, with a clover glyph""" 290 | RIGHT_CONTROL = 0xE4 291 | """Control modifier right of the spacebar""" 292 | RIGHT_SHIFT = 0xE5 293 | """Shift modifier right of the spacebar""" 294 | RIGHT_ALT = 0xE6 295 | """Alt modifier right of the spacebar""" 296 | RIGHT_GUI = 0xE7 297 | """GUI modifier right of the spacebar""" 298 | 299 | @classmethod 300 | def modifier_bit(cls, keycode: int) -> int: 301 | """Return the modifer bit to be set in an HID keycode report if this is a 302 | modifier key; otherwise return 0.""" 303 | return 1 << (keycode - 0xE0) if cls.LEFT_CONTROL <= keycode <= cls.RIGHT_GUI else 0 304 | -------------------------------------------------------------------------------- /adafruit_hid/mouse.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_hid.mouse.Mouse` 7 | ==================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | from . import find_device 13 | 14 | try: 15 | from typing import Sequence 16 | 17 | import usb_hid 18 | except ImportError: 19 | pass 20 | 21 | 22 | class Mouse: 23 | """Send USB HID mouse reports.""" 24 | 25 | LEFT_BUTTON = 1 26 | """Left mouse button.""" 27 | RIGHT_BUTTON = 2 28 | """Right mouse button.""" 29 | MIDDLE_BUTTON = 4 30 | """Middle mouse button.""" 31 | BACK_BUTTON = 8 32 | """Back mouse button.""" 33 | FORWARD_BUTTON = 16 34 | """Forward mouse button.""" 35 | 36 | def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: 37 | """Create a Mouse object that will send USB mouse HID reports. 38 | 39 | :param timeout: Time in seconds to wait for USB to become ready before timing out. 40 | Defaults to None to wait indefinitely. 41 | 42 | Devices can be a sequence of devices that includes a keyboard device or a keyboard device 43 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 44 | ``usage``. 45 | """ 46 | self._mouse_device = find_device(devices, usage_page=0x1, usage=0x02, timeout=timeout) 47 | 48 | # Reuse this bytearray to send mouse reports. 49 | # report[0] buttons pressed (LEFT, MIDDLE, RIGHT) 50 | # report[1] x movement 51 | # report[2] y movement 52 | # report[3] wheel movement 53 | self.report = bytearray(4) 54 | 55 | def press(self, buttons: int) -> None: 56 | """Press the given mouse buttons. 57 | 58 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 59 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 60 | 61 | Examples:: 62 | 63 | # Press the left button. 64 | m.press(Mouse.LEFT_BUTTON) 65 | 66 | # Press the left and right buttons simultaneously. 67 | m.press(Mouse.LEFT_BUTTON | Mouse.RIGHT_BUTTON) 68 | """ 69 | self.report[0] |= buttons 70 | self._send_no_move() 71 | 72 | def release(self, buttons: int) -> None: 73 | """Release the given mouse buttons. 74 | 75 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 76 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 77 | """ 78 | self.report[0] &= ~buttons 79 | self._send_no_move() 80 | 81 | def release_all(self) -> None: 82 | """Release all the mouse buttons.""" 83 | self.report[0] = 0 84 | self._send_no_move() 85 | 86 | def click(self, buttons: int) -> None: 87 | """Press and release the given mouse buttons. 88 | 89 | :param buttons: a bitwise-or'd combination of ``LEFT_BUTTON``, 90 | ``MIDDLE_BUTTON``, and ``RIGHT_BUTTON``. 91 | 92 | Examples:: 93 | 94 | # Click the left button. 95 | m.click(Mouse.LEFT_BUTTON) 96 | 97 | # Double-click the left button. 98 | m.click(Mouse.LEFT_BUTTON) 99 | m.click(Mouse.LEFT_BUTTON) 100 | """ 101 | self.press(buttons) 102 | self.release(buttons) 103 | 104 | def move(self, x: int = 0, y: int = 0, wheel: int = 0) -> None: 105 | """Move the mouse and turn the wheel as directed. 106 | 107 | :param x: Move the mouse along the x axis. Negative is to the left, positive 108 | is to the right. 109 | :param y: Move the mouse along the y axis. Negative is upwards on the display, 110 | positive is downwards. 111 | :param wheel: Rotate the wheel this amount. Negative is toward the user, positive 112 | is away from the user. The scrolling effect depends on the host. 113 | 114 | Examples:: 115 | 116 | # Move 100 to the left. Do not move up and down. Do not roll the scroll wheel. 117 | m.move(-100, 0, 0) 118 | # Same, with keyword arguments. 119 | m.move(x=-100) 120 | 121 | # Move diagonally to the upper right. 122 | m.move(50, 20) 123 | # Same. 124 | m.move(x=50, y=-20) 125 | 126 | # Roll the mouse wheel away from the user. 127 | m.move(wheel=1) 128 | """ 129 | # Send multiple reports if necessary to move or scroll requested amounts. 130 | while x != 0 or y != 0 or wheel != 0: 131 | partial_x = self._limit(x) 132 | partial_y = self._limit(y) 133 | partial_wheel = self._limit(wheel) 134 | self.report[1] = partial_x & 0xFF 135 | self.report[2] = partial_y & 0xFF 136 | self.report[3] = partial_wheel & 0xFF 137 | self._mouse_device.send_report(self.report) 138 | x -= partial_x 139 | y -= partial_y 140 | wheel -= partial_wheel 141 | 142 | def _send_no_move(self) -> None: 143 | """Send a button-only report.""" 144 | self.report[1] = 0 145 | self.report[2] = 0 146 | self.report[3] = 0 147 | self._mouse_device.send_report(self.report) 148 | 149 | @staticmethod 150 | def _limit(dist: int) -> int: 151 | return min(127, max(-127, dist)) 152 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_HID/e954e8b1637e42dba1c211b5919233c4e761d8b7/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/favicon.ico.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2018 Phillip Torrone for Adafruit Industries 2 | 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | 2 | .. If you created a package, create one automodule per module in the package. 3 | 4 | API Reference 5 | ############# 6 | 7 | .. automodule:: adafruit_hid 8 | :members: 9 | 10 | .. automodule:: adafruit_hid.keyboard 11 | :members: 12 | 13 | .. automodule:: adafruit_hid.keycode 14 | :members: 15 | 16 | .. automodule:: adafruit_hid.keyboard_layout_us 17 | :members: 18 | :inherited-members: 19 | 20 | .. automodule:: adafruit_hid.keyboard_layout_base 21 | :members: 22 | 23 | .. automodule:: adafruit_hid.mouse 24 | :members: 25 | 26 | .. automodule:: adafruit_hid.consumer_control 27 | :members: 28 | 29 | .. automodule:: adafruit_hid.consumer_control_code 30 | :members: 31 | -------------------------------------------------------------------------------- /docs/api.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | import datetime 6 | import os 7 | import sys 8 | 9 | sys.path.insert(0, os.path.abspath("..")) 10 | 11 | # -- General configuration ------------------------------------------------ 12 | 13 | # Add any Sphinx extension module names here, as strings. They can be 14 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 15 | # ones. 16 | extensions = [ 17 | "sphinx.ext.autodoc", 18 | "sphinxcontrib.jquery", 19 | "sphinx.ext.intersphinx", 20 | "sphinx.ext.napoleon", 21 | "sphinx.ext.todo", 22 | ] 23 | 24 | # TODO: Please Read! 25 | # Uncomment the below if you use native CircuitPython modules such as 26 | # digitalio, micropython and busio. List the modules you use. Without it, the 27 | # autodoc module docs will fail to generate with a warning. 28 | autodoc_mock_imports = ["usb_hid"] 29 | 30 | autodoc_preserve_defaults = True 31 | 32 | 33 | intersphinx_mapping = { 34 | "python": ("https://docs.python.org/3", None), 35 | "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), 36 | } 37 | 38 | # Show the docstring from both the class and its __init__() method. 39 | autoclass_content = "both" 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ["_templates"] 43 | 44 | source_suffix = ".rst" 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "Adafruit HID Library" 51 | creation_year = "2017" 52 | current_year = str(datetime.datetime.now().year) 53 | year_duration = ( 54 | current_year if current_year == creation_year else creation_year + " - " + current_year 55 | ) 56 | copyright = year_duration + " Scott Shawcroft" 57 | author = "Scott Shawcroft" 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | version = "1.0" 65 | # The full version, including alpha/beta/rc tags. 66 | release = "1.0" 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = "en" 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | # This patterns also effect to html_static_path and html_extra_path 78 | exclude_patterns = [ 79 | "_build", 80 | "Thumbs.db", 81 | ".DS_Store", 82 | ".env", 83 | "CODE_OF_CONDUCT.md", 84 | ] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | # 89 | default_role = "any" 90 | 91 | # If true, '()' will be appended to :func: etc. cross-reference text. 92 | # 93 | add_function_parentheses = True 94 | 95 | # The name of the Pygments (syntax highlighting) style to use. 96 | pygments_style = "sphinx" 97 | 98 | # If true, `todo` and `todoList` produce output, else they produce nothing. 99 | todo_include_todos = False 100 | 101 | # If this is True, todo emits a warning for each TODO entries. The default is False. 102 | todo_emit_warnings = True 103 | 104 | napoleon_numpy_docstring = False 105 | 106 | # -- Options for HTML output ---------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | # 111 | import sphinx_rtd_theme 112 | 113 | html_theme = "sphinx_rtd_theme" 114 | 115 | # Add any paths that contain custom static files (such as style sheets) here, 116 | # relative to this directory. They are copied after the builtin static files, 117 | # so a file named "default.css" will overwrite the builtin "default.css". 118 | html_static_path = ["_static"] 119 | 120 | # The name of an image file (relative to this directory) to use as a favicon of 121 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | # 124 | html_favicon = "_static/favicon.ico" 125 | 126 | # Output file base name for HTML help builder. 127 | htmlhelp_basename = "AdafruitHIDLibrarydoc" 128 | 129 | # -- Options for LaTeX output --------------------------------------------- 130 | 131 | latex_elements = { 132 | # The paper size ('letterpaper' or 'a4paper'). 133 | # 'papersize': 'letterpaper', 134 | # The font size ('10pt', '11pt' or '12pt'). 135 | # 'pointsize': '10pt', 136 | # Additional stuff for the LaTeX preamble. 137 | # 'preamble': '', 138 | # Latex figure (float) alignment 139 | # 'figure_align': 'htbp', 140 | } 141 | 142 | # Grouping the document tree into LaTeX files. List of tuples 143 | # (source start file, target name, title, 144 | # author, documentclass [howto, manual, or own class]). 145 | latex_documents = [ 146 | ( 147 | master_doc, 148 | "AdafruitHIDLibrary.tex", 149 | "Adafruit HID Library Documentation", 150 | "Scott Shawcroft", 151 | "manual", 152 | ), 153 | ] 154 | 155 | # -- Options for manual page output --------------------------------------- 156 | 157 | # One entry per manual page. List of tuples 158 | # (source start file, name, description, authors, manual section). 159 | man_pages = [ 160 | ( 161 | master_doc, 162 | "adafruitHIDlibrary", 163 | "Adafruit HID Library Documentation", 164 | [author], 165 | 1, 166 | ) 167 | ] 168 | 169 | # -- Options for Texinfo output ------------------------------------------- 170 | 171 | # Grouping the document tree into Texinfo files. List of tuples 172 | # (source start file, target name, title, author, 173 | # dir menu entry, description, category) 174 | texinfo_documents = [ 175 | ( 176 | master_doc, 177 | "AdafruitHIDLibrary", 178 | "Adafruit HID Library Documentation", 179 | author, 180 | "AdafruitHIDLibrary", 181 | "One line description of project.", 182 | "Miscellaneous", 183 | ), 184 | ] 185 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Simple test 2 | ------------ 3 | 4 | Ensure your device works with this simple test. 5 | 6 | .. literalinclude:: ../examples/hid_simpletest.py 7 | :caption: examples/hid_simpletest.py 8 | :linenos: 9 | 10 | Keyboard Shortcuts 11 | ------------------- 12 | 13 | Send ALT+Tab for swapping windows, and CTRL+K for searching in a browser. 14 | 15 | .. literalinclude:: ../examples/hid_keyboard_shortcuts.py 16 | :caption: examples/hid_keyboard_shortcuts.py 17 | :linenos: 18 | 19 | Keyboard Layout 20 | --------------- 21 | 22 | While the ``Keyboard`` class itself provides easy way for sending key shortcuts, for writing more 23 | complex text you may want to use a ``KeyboardLayout`` and a ``.write()`` method. 24 | 25 | It is also possible to adjust the typing speed by specifying ``delay`` between key presses. 26 | 27 | .. literalinclude:: ../examples/hid_keyboard_layout.py 28 | :caption: examples/hid_keyboard_layout.py 29 | :emphasize-lines: 12-13,29,33 30 | :linenos: 31 | 32 | Simple Gamepad 33 | --------------- 34 | 35 | Send gamepad buttons and joystick to the host. 36 | 37 | .. literalinclude:: ../examples/hid_simple_gamepad.py 38 | :caption: examples/hid_simple_gamepad.py 39 | :linenos: 40 | 41 | HID Joywing 42 | ------------ 43 | 44 | Use Joy FeatherWing to drive Gamepad. 45 | 46 | .. literalinclude:: ../examples/hid_joywing_gamepad.py 47 | :caption: examples/hid_joywing_gamepad.py 48 | :linenos: 49 | 50 | Consumer Control Brightness 51 | ---------------------------- 52 | 53 | Send brightness up and down consumer codes to the host. 54 | 55 | .. literalinclude:: ../examples/hid_consumer_control_brightness.py 56 | :caption: examples/hid_consumer_control_brightness.py 57 | :linenos: 58 | -------------------------------------------------------------------------------- /docs/examples.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Table of Contents 4 | ================= 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :hidden: 9 | 10 | self 11 | 12 | .. toctree:: 13 | :caption: Examples 14 | 15 | examples 16 | 17 | .. toctree:: 18 | :caption: API Reference 19 | :maxdepth: 3 20 | 21 | api 22 | 23 | .. toctree:: 24 | :caption: Tutorials 25 | 26 | .. toctree:: 27 | :caption: Related Products 28 | 29 | .. toctree:: 30 | :caption: Other Links 31 | 32 | Download from GitHub 33 | Download Library Bundle 34 | CircuitPython Reference Documentation 35 | CircuitPython Support Forum 36 | Discord Chat 37 | Adafruit Learning System 38 | Adafruit Blog 39 | Adafruit Store 40 | 41 | Indices and tables 42 | ================== 43 | 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | -------------------------------------------------------------------------------- /docs/index.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | sphinx 6 | sphinxcontrib-jquery 7 | sphinx-rtd-theme 8 | -------------------------------------------------------------------------------- /examples/hid_consumer_control_brightness.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Tim C for Adafruit Industries 2 | # SPDX-License-Identifier: MIT 3 | 4 | import time 5 | 6 | import board 7 | import digitalio 8 | import usb_hid 9 | 10 | from adafruit_hid.consumer_control import ConsumerControl 11 | from adafruit_hid.consumer_control_code import ConsumerControlCode 12 | 13 | cc = ConsumerControl(usb_hid.devices) 14 | 15 | # define buttons. these can be any physical switches/buttons, but the values 16 | # here work out-of-the-box with a FunHouse UP and DOWN buttons. 17 | button_up = digitalio.DigitalInOut(board.BUTTON_UP) 18 | button_up.switch_to_input(pull=digitalio.Pull.DOWN) 19 | 20 | button_down = digitalio.DigitalInOut(board.BUTTON_DOWN) 21 | button_down.switch_to_input(pull=digitalio.Pull.DOWN) 22 | 23 | while True: 24 | if button_up.value: 25 | print("Button up pressed!") 26 | # send brightness up button press 27 | cc.send(ConsumerControlCode.BRIGHTNESS_INCREMENT) 28 | 29 | if button_down.value: 30 | print("Button down pressed!") 31 | # send brightness down button press 32 | cc.send(ConsumerControlCode.BRIGHTNESS_DECREMENT) 33 | 34 | time.sleep(0.1) 35 | -------------------------------------------------------------------------------- /examples/hid_gamepad.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `Gamepad` 7 | ==================================================== 8 | 9 | * Author(s): Dan Halbert 10 | """ 11 | 12 | import struct 13 | import time 14 | 15 | from adafruit_hid import find_device 16 | 17 | 18 | class Gamepad: 19 | """Emulate a generic gamepad controller with 16 buttons, 20 | numbered 1-16, and two joysticks, one controlling 21 | ``x` and ``y`` values, and the other controlling ``z`` and 22 | ``r_z`` (z rotation or ``Rz``) values. 23 | 24 | The joystick values could be interpreted 25 | differently by the receiving program: those are just the names used here. 26 | The joystick values are in the range -127 to 127.""" 27 | 28 | def __init__(self, devices): 29 | """Create a Gamepad object that will send USB gamepad HID reports. 30 | 31 | Devices can be a list of devices that includes a gamepad device or a gamepad device 32 | itself. A device is any object that implements ``send_report()``, ``usage_page`` and 33 | ``usage``. 34 | """ 35 | self._gamepad_device = find_device(devices, usage_page=0x1, usage=0x05) 36 | 37 | # Reuse this bytearray to send mouse reports. 38 | # Typically controllers start numbering buttons at 1 rather than 0. 39 | # report[0] buttons 1-8 (LSB is button 1) 40 | # report[1] buttons 9-16 41 | # report[2] joystick 0 x: -127 to 127 42 | # report[3] joystick 0 y: -127 to 127 43 | # report[4] joystick 1 x: -127 to 127 44 | # report[5] joystick 1 y: -127 to 127 45 | self._report = bytearray(6) 46 | 47 | # Remember the last report as well, so we can avoid sending 48 | # duplicate reports. 49 | self._last_report = bytearray(6) 50 | 51 | # Store settings separately before putting into report. Saves code 52 | # especially for buttons. 53 | self._buttons_state = 0 54 | self._joy_x = 0 55 | self._joy_y = 0 56 | self._joy_z = 0 57 | self._joy_r_z = 0 58 | 59 | # Send an initial report to test if HID device is ready. 60 | # If not, wait a bit and try once more. 61 | try: 62 | self.reset_all() 63 | except OSError: 64 | time.sleep(1) 65 | self.reset_all() 66 | 67 | def press_buttons(self, *buttons): 68 | """Press and hold the given buttons.""" 69 | for button in buttons: 70 | self._buttons_state |= 1 << self._validate_button_number(button) - 1 71 | self._send() 72 | 73 | def release_buttons(self, *buttons): 74 | """Release the given buttons.""" 75 | for button in buttons: 76 | self._buttons_state &= ~(1 << self._validate_button_number(button) - 1) 77 | self._send() 78 | 79 | def release_all_buttons(self): 80 | """Release all the buttons.""" 81 | 82 | self._buttons_state = 0 83 | self._send() 84 | 85 | def click_buttons(self, *buttons): 86 | """Press and release the given buttons.""" 87 | self.press_buttons(*buttons) 88 | self.release_buttons(*buttons) 89 | 90 | def move_joysticks(self, x=None, y=None, z=None, r_z=None): 91 | """Set and send the given joystick values. 92 | The joysticks will remain set with the given values until changed 93 | 94 | One joystick provides ``x`` and ``y`` values, 95 | and the other provides ``z`` and ``r_z`` (z rotation). 96 | Any values left as ``None`` will not be changed. 97 | 98 | All values must be in the range -127 to 127 inclusive. 99 | 100 | Examples:: 101 | 102 | # Change x and y values only. 103 | gp.move_joysticks(x=100, y=-50) 104 | 105 | # Reset all joystick values to center position. 106 | gp.move_joysticks(0, 0, 0, 0) 107 | """ 108 | if x is not None: 109 | self._joy_x = self._validate_joystick_value(x) 110 | if y is not None: 111 | self._joy_y = self._validate_joystick_value(y) 112 | if z is not None: 113 | self._joy_z = self._validate_joystick_value(z) 114 | if r_z is not None: 115 | self._joy_r_z = self._validate_joystick_value(r_z) 116 | self._send() 117 | 118 | def reset_all(self): 119 | """Release all buttons and set joysticks to zero.""" 120 | self._buttons_state = 0 121 | self._joy_x = 0 122 | self._joy_y = 0 123 | self._joy_z = 0 124 | self._joy_r_z = 0 125 | self._send(always=True) 126 | 127 | def _send(self, always=False): 128 | """Send a report with all the existing settings. 129 | If ``always`` is ``False`` (the default), send only if there have been changes. 130 | """ 131 | struct.pack_into( 132 | "