# sshkeyboard
The only keyboard event callback library that works _everywhere_, even when
used through an [SSH](https://en.wikipedia.org/wiki/Secure_Shell) connection
(hence the name).
It works with headless computers and servers, or for example inside Windows
Subsystem for Linux (WSL 2). One good use case is controlling Raspberry Pi
based robots or RC cars through SSH. Note that this library can also be used
locally without an SSH connection.
It does not depend on X server, uinput, root access (sudo) or
any external dependencies.
Supports [asyncio](https://docs.python.org/3/library/asyncio.html) and
sequential/concurrent callback modes. For Python 3.6+.
[Documentation](https://sshkeyboard.readthedocs.io) -
[Github source](https://github.com/ollipal/sshkeyboard) -
[PyPI](https://pypi.org/project/sshkeyboard/) -
[Reference](https://sshkeyboard.readthedocs.io/en/latest/reference.html) -
[![Downloads](https://static.pepy.tech/personalized-badge/sshkeyboard?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/sshkeyboard)
## Quick start
Installation:
```text
pip install sshkeyboard
```
Simple example to fire events when a key is pressed or released.
`esc` key ends listening by default:
```python
from sshkeyboard import listen_keyboard
def press(key):
print(f"'{key}' pressed")
def release(key):
print(f"'{key}' released")
listen_keyboard(
on_press=press,
on_release=release,
)
```
Output:
```text
$ python example.py
'a' pressed
'a' released
```
## How it works
The sshkeyboard library works without
[X server](https://en.wikipedia.org/wiki/X_Window_System)
and [uinput](https://www.kernel.org/doc/html/v4.12/input/uinput.html).
On Unix based systems (such as Linux, macOS) it works by parsing characters
from [sys.stdin](https://docs.python.org/3/library/sys.html#sys.stdin). This
is done with [fcntl](https://docs.python.org/3/library/fcntl.html) and
[termios](https://docs.python.org/3/library/termios.html) standard library
modules.
On Windows [msvcrt](https://docs.python.org/3/library/msvcrt.html) standard
library module is used to read user input. The Windows support is still new,
so please create [an issue](https://github.com/ollipal/sshkeyboard/issues)
if you run into problems.
This behaviour allows it to work where other libraries like
[pynput](#comparison-to-other-keyboard-libraries) or
[keyboard](#comparison-to-other-keyboard-libraries) do not work, but
it comes with some **limitations**, mainly:
1. Holding multiple keys down at the same time does not work, the library
releases the previous keys when a new one is pressed. Releasing keys also
happens after a short delay, and some key presses can get lost if the same
key gets spammed fast.
2. Some keys do not write to `sys.stdin` when pressed, such as `Ctrl`,
`Shift`, `Caps Lock`, `Alt` and `Windows`/`Command`/`Super` key. That is
why this library does not attempt to parse those even if they could be
technically be parsed in some cases
## Advanced use
### Sequential mode
Normally this library allows `on_press` and `on_release` callbacks to be run
concurrently. This means that by running:
```python
import time
from sshkeyboard import listen_keyboard
def press(key):
print(f"'{key}' pressed")
time.sleep(3)
print(f"'{key}' slept")
listen_keyboard(on_press=press)
```
and pressing `"a"`, `"s"` and `"d"` keys will log:
```text
'a' pressed
's' pressed
'd' pressed
'a' slept
's' slept
'd' slept
```
But sometimes you don't want to allow the callbacks to overlap, then
you should set `sequential` parameter to `True`:
```python
# ...
listen_keyboard(
on_press=press,
sequential=True,
)
```
Then pressing `"a"`, `"s"` and `"d"` keys will log:
```text
'a' pressed
'a' slept
's' pressed
's' slept
'd' pressed
'd' slept
```
### Asyncio
You can also use asynchronous functions as `on_press` / `on_release` callbacks
with `listen_keyboard`:
```python
import asyncio
from sshkeyboard import listen_keyboard
async def press(key):
print(f"'{key}' pressed")
await asyncio.sleep(3)
print(f"'{key}' slept")
listen_keyboard(on_press=press)
```
> **NOTE** remember to use `await asyncio.sleep(...)` in async callbacks
instead of `time.sleep(...)` or the timings will fail:
### Mixing asynchronous and concurrent callbacks
`listen_keyboard` also supports mixing asynchronous and concurrent callbacks:
```python
import asyncio
import time
from sshkeyboard import listen_keyboard
async def press(key):
print(f"'{key}' pressed")
await asyncio.sleep(3)
print(f"'{key}' press slept")
def release(key):
print(f"'{key}' relased")
time.sleep(3)
print(f"'{key}' release slept")
listen_keyboard(
on_press=press,
on_release=release,
)
```
Here pressing `"a"` and `"s"` will log:
```text
'a' pressed
'a' relased
's' pressed
's' relased
'a' press slept
's' press slept
'a' release slept
's' release slept
```
And with `sequential=True`:
```python
# ...
listen_keyboard(
on_press=press,
on_release=release,
sequential=True,
)
```
will log:
```text
'a' pressed
'a' press slept
'a' relased
'a' release slept
's' pressed
's' press slept
's' relased
's' release slept
```
> **NOTE** remember to use `await asyncio.sleep(...)` in async callbacks
instead of `time.sleep(...)` or the timings will fail:
### Stop listening
You can change the key that ends the listening by giving `until` parameter,
which defaults to `"esc"`:
```python
# ...
listen_keyboard(
on_press=press,
until="space",
)
```
You also can manually stop listening by calling `stop_listening()` from the
callback or from some other function:
```python
from sshkeyboard import listen_keyboard, stop_listening
def press(key):
print(f"'{key}' pressed")
if key == "z":
stop_listening()
listen_keyboard(on_press=press)
```
`until` can be also set to `None`. This means that listening ends only with
`stop_listening()` or if an error has been raised.
### Troubleshooting
If some keys do not seem to register correctly, try turning the debug mode on.
This will add logs if some keys are skipped intentionally:
```python
# ...
listen_keyboard(
on_press=press,
debug=True,
)
```
If one key press causes multiple `on_press` / `on_release` callbacks or if
releasing happens too slowly, you can try to tweak the default timing
parameters:
```python
# ...
listen_keyboard(
on_press=press,
delay_second_char=0.75,
delay_other_chars=0.05,
)
```
### More
Check out the full
[reference](https://sshkeyboard.readthedocs.io/en/latest/reference.html)
for more functions and parameters such as:
- `lower` parameter
- `sleep` parameter
- `max_thread_pool_workers` parameter
- `listen_keyboard_manual` function
Direct links to functions:
- [listen_keyboard](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.listen_keyboard)
- [stop_listening](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.stop_listening)
- [listen_keyboard_manual](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.listen_keyboard_manual)
## Development
This sections explains how to build the documentation and how to run the
[pre-commit script](https://github.com/ollipal/sshkeyboard/blob/main/pre-commit)
locally. This helps if you want to create
[a pull request](https://github.com/ollipal/sshkeyboard/pulls)
or if you just want to try things out.
Building the documentations allows you to build all of the files served on the
[documentation](https://sshkeyboard.readthedocs.io) site locally.
The
[pre-commit script](https://github.com/ollipal/sshkeyboard/blob/main/pre-commit)
handles running tests, formatting and
linting before each Git commit. These same checks also run automatically on
[Github Actions](https://github.com/ollipal/sshkeyboard/blob/main/.github/workflows/main.yml).
Start by cloning this library, and change directory to the project root:
```text
git clone git@github.com:ollipal/sshkeyboard.git
cd sshkeyboard
```
Optionally, create and activate a virtual environment at the root of the
project (you might need to use `python3` keyword instead of `python`):
```text
python -m venv .env
source .env/bin/activate
```
(Later you can deactivate the virtual environment with: `deactivate`)
To build the documentation or run the pre-commit script locally, you need
to install the development dependencies:
```text
pip install -r dev-requirements.txt
```
### Documentation
To build the documentation locally, first change into `docs/` directory:
```text
cd docs
```
Then to build the documentation, call:
```text
make html
```
Now you should have a new `docs/build/` directory, and you can open
`<your-clone-path>/sshkeyboard/docs/build/html/index.html` from your browser.
You can force the rebuild by running:
```text
rm -rf build/ && make html
```
You can change the documentation content by changing `README.md` or files from
`src/` or `docs/source/`. If you are mainly changing contents from
`docs/source/`, you can enable automatic re-building by running:
```text
sphinx-autobuild ./source/ ./build/html/
```
### Running the pre-commit script
You can run the **tests**
([tox](https://tox.wiki/en/latest/index.html),
[pytest](https://docs.pytest.org)), **formatting**
([black](https://black.readthedocs.io/en/stable/),
[isort](https://pycqa.github.io/isort/)) and **linting**
([pflake8](https://github.com/csachs/pyproject-flake8),
[pep8-naming](https://github.com/PyCQA/pep8-naming),
[codespell](https://github.com/codespell-project/codespell),
[markdownlint](https://github.com/markdownlint/markdownlint)) simply by
executing:
```text
./pre-commit
```
Now if you want to automatically run these when you call `git commit`, copy
the script into `.git/hooks/` directory:
```text
cp pre-commit .git/hooks
```
> **NOTE**: this process does not run `markdownlint` by default as it
requires [Ruby](https://www.ruby-lang.org/en/) to be installed. If you want
to run `markdownlint` locally as well,
[install Ruby](https://www.ruby-lang.org/en/documentation/installation/)
and install markdown lint with `gem install mdl -v 0.11.0`. Then from
`pre-commit` change `RUN_MDL=false` to `RUN_MDL=true`. (You need to copy the
file again into `.git/hooks/` if you did that earlier)
## Comparison to other keyboard libraries
The other keyboard libraries work by reading proper keycodes from the system.
This means that they usually require either
[X server](https://en.wikipedia.org/wiki/X_Window_System) or
[uinput](https://www.kernel.org/doc/html/v4.12/input/uinput.html), so they do
not work over SSH. But this means they do not have the same
[limitations](#how-it-works) as this library.
They usually can also support more features such as pressing the keys instead
of just reacting to user input.
I have good experiences from these libraries:
- [pynput](https://pynput.readthedocs.io/en/latest/)
- [keyboard](https://github.com/boppreh/keyboard) (requires sudo)
Raw data
{
"_id": null,
"home_page": "",
"name": "sshkeyboard",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": "",
"keywords": "keyboard,listener,ssh,callback,headless,wsl,wsl2,server,x server,uinput,Raspberry Pi,pynput",
"author": "Olli Paloviita",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/e3/7b/d78e6ade4bb4680d0a610ed02047c4de04db62de8864193bf842c59c47cb/sshkeyboard-2.3.1.tar.gz",
"platform": "",
"description": "# sshkeyboard\n\nThe only keyboard event callback library that works _everywhere_, even when\nused through an [SSH](https://en.wikipedia.org/wiki/Secure_Shell) connection\n(hence the name).\n\nIt works with headless computers and servers, or for example inside Windows\nSubsystem for Linux (WSL 2). One good use case is controlling Raspberry Pi\nbased robots or RC cars through SSH. Note that this library can also be used\nlocally without an SSH connection.\n\nIt does not depend on X server, uinput, root access (sudo) or\nany external dependencies.\n\nSupports [asyncio](https://docs.python.org/3/library/asyncio.html) and\nsequential/concurrent callback modes. For Python 3.6+.\n\n[Documentation](https://sshkeyboard.readthedocs.io) -\n[Github source](https://github.com/ollipal/sshkeyboard) -\n[PyPI](https://pypi.org/project/sshkeyboard/) -\n[Reference](https://sshkeyboard.readthedocs.io/en/latest/reference.html) -\n[![Downloads](https://static.pepy.tech/personalized-badge/sshkeyboard?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/sshkeyboard)\n\n## Quick start\n\nInstallation:\n\n```text\npip install sshkeyboard\n```\n\nSimple example to fire events when a key is pressed or released.\n`esc` key ends listening by default:\n\n```python\nfrom sshkeyboard import listen_keyboard\n\ndef press(key):\n print(f\"'{key}' pressed\")\n\ndef release(key):\n print(f\"'{key}' released\")\n\nlisten_keyboard(\n on_press=press,\n on_release=release,\n)\n```\n\nOutput:\n\n```text\n$ python example.py\n'a' pressed\n'a' released\n```\n\n## How it works\n\nThe sshkeyboard library works without\n[X server](https://en.wikipedia.org/wiki/X_Window_System)\nand [uinput](https://www.kernel.org/doc/html/v4.12/input/uinput.html).\n\nOn Unix based systems (such as Linux, macOS) it works by parsing characters\nfrom [sys.stdin](https://docs.python.org/3/library/sys.html#sys.stdin). This\nis done with [fcntl](https://docs.python.org/3/library/fcntl.html) and\n[termios](https://docs.python.org/3/library/termios.html) standard library\nmodules.\n\nOn Windows [msvcrt](https://docs.python.org/3/library/msvcrt.html) standard\nlibrary module is used to read user input. The Windows support is still new,\nso please create [an issue](https://github.com/ollipal/sshkeyboard/issues)\nif you run into problems.\n\nThis behaviour allows it to work where other libraries like\n[pynput](#comparison-to-other-keyboard-libraries) or\n[keyboard](#comparison-to-other-keyboard-libraries) do not work, but\nit comes with some **limitations**, mainly:\n\n1. Holding multiple keys down at the same time does not work, the library\n releases the previous keys when a new one is pressed. Releasing keys also\n happens after a short delay, and some key presses can get lost if the same\n key gets spammed fast.\n2. Some keys do not write to `sys.stdin` when pressed, such as `Ctrl`,\n `Shift`, `Caps Lock`, `Alt` and `Windows`/`Command`/`Super` key. That is\n why this library does not attempt to parse those even if they could be\n technically be parsed in some cases\n\n## Advanced use\n\n### Sequential mode\n\nNormally this library allows `on_press` and `on_release` callbacks to be run\nconcurrently. This means that by running:\n\n```python\nimport time\nfrom sshkeyboard import listen_keyboard\n\ndef press(key):\n print(f\"'{key}' pressed\")\n time.sleep(3)\n print(f\"'{key}' slept\")\n\nlisten_keyboard(on_press=press)\n```\n\nand pressing `\"a\"`, `\"s\"` and `\"d\"` keys will log:\n\n```text\n'a' pressed\n's' pressed\n'd' pressed\n'a' slept\n's' slept\n'd' slept\n```\n\nBut sometimes you don't want to allow the callbacks to overlap, then\nyou should set `sequential` parameter to `True`:\n\n```python\n# ...\nlisten_keyboard(\n on_press=press,\n sequential=True,\n)\n```\n\nThen pressing `\"a\"`, `\"s\"` and `\"d\"` keys will log:\n\n```text\n'a' pressed\n'a' slept\n's' pressed\n's' slept\n'd' pressed\n'd' slept\n```\n\n### Asyncio\n\nYou can also use asynchronous functions as `on_press` / `on_release` callbacks\nwith `listen_keyboard`:\n\n```python\nimport asyncio\nfrom sshkeyboard import listen_keyboard\n\nasync def press(key):\n print(f\"'{key}' pressed\")\n await asyncio.sleep(3)\n print(f\"'{key}' slept\")\n\nlisten_keyboard(on_press=press)\n```\n\n> **NOTE** remember to use `await asyncio.sleep(...)` in async callbacks\ninstead of `time.sleep(...)` or the timings will fail:\n\n### Mixing asynchronous and concurrent callbacks\n\n`listen_keyboard` also supports mixing asynchronous and concurrent callbacks:\n\n```python\nimport asyncio\nimport time\nfrom sshkeyboard import listen_keyboard\n\nasync def press(key):\n print(f\"'{key}' pressed\")\n await asyncio.sleep(3)\n print(f\"'{key}' press slept\")\n\ndef release(key):\n print(f\"'{key}' relased\")\n time.sleep(3)\n print(f\"'{key}' release slept\")\n\nlisten_keyboard(\n on_press=press,\n on_release=release,\n)\n```\n\nHere pressing `\"a\"` and `\"s\"` will log:\n\n```text\n'a' pressed\n'a' relased\n's' pressed\n's' relased\n'a' press slept\n's' press slept\n'a' release slept\n's' release slept\n```\n\nAnd with `sequential=True`:\n\n```python\n# ...\nlisten_keyboard(\n on_press=press,\n on_release=release,\n sequential=True,\n)\n```\n\nwill log:\n\n```text\n'a' pressed\n'a' press slept\n'a' relased\n'a' release slept\n's' pressed\n's' press slept\n's' relased\n's' release slept\n```\n\n> **NOTE** remember to use `await asyncio.sleep(...)` in async callbacks\ninstead of `time.sleep(...)` or the timings will fail:\n\n### Stop listening\n\nYou can change the key that ends the listening by giving `until` parameter,\nwhich defaults to `\"esc\"`:\n\n```python\n# ...\nlisten_keyboard(\n on_press=press,\n until=\"space\",\n)\n```\n\nYou also can manually stop listening by calling `stop_listening()` from the\ncallback or from some other function:\n\n```python\nfrom sshkeyboard import listen_keyboard, stop_listening\n\ndef press(key):\n print(f\"'{key}' pressed\")\n if key == \"z\":\n stop_listening()\n\nlisten_keyboard(on_press=press)\n```\n\n`until` can be also set to `None`. This means that listening ends only with\n`stop_listening()` or if an error has been raised.\n\n### Troubleshooting\n\nIf some keys do not seem to register correctly, try turning the debug mode on.\nThis will add logs if some keys are skipped intentionally:\n\n```python\n# ...\nlisten_keyboard(\n on_press=press,\n debug=True,\n)\n```\n\nIf one key press causes multiple `on_press` / `on_release` callbacks or if\nreleasing happens too slowly, you can try to tweak the default timing\nparameters:\n\n```python\n# ...\nlisten_keyboard(\n on_press=press,\n delay_second_char=0.75,\n delay_other_chars=0.05,\n)\n```\n\n### More\n\nCheck out the full\n[reference](https://sshkeyboard.readthedocs.io/en/latest/reference.html)\nfor more functions and parameters such as:\n\n- `lower` parameter\n- `sleep` parameter\n- `max_thread_pool_workers` parameter\n- `listen_keyboard_manual` function\n\nDirect links to functions:\n\n- [listen_keyboard](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.listen_keyboard)\n- [stop_listening](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.stop_listening)\n- [listen_keyboard_manual](https://sshkeyboard.readthedocs.io/en/latest/reference.html#sshkeyboard.listen_keyboard_manual)\n\n## Development\n\nThis sections explains how to build the documentation and how to run the\n[pre-commit script](https://github.com/ollipal/sshkeyboard/blob/main/pre-commit)\nlocally. This helps if you want to create\n[a pull request](https://github.com/ollipal/sshkeyboard/pulls)\nor if you just want to try things out.\n\nBuilding the documentations allows you to build all of the files served on the\n[documentation](https://sshkeyboard.readthedocs.io) site locally.\n\nThe\n[pre-commit script](https://github.com/ollipal/sshkeyboard/blob/main/pre-commit)\nhandles running tests, formatting and\nlinting before each Git commit. These same checks also run automatically on\n[Github Actions](https://github.com/ollipal/sshkeyboard/blob/main/.github/workflows/main.yml).\n\nStart by cloning this library, and change directory to the project root:\n\n```text\ngit clone git@github.com:ollipal/sshkeyboard.git\ncd sshkeyboard\n```\n\nOptionally, create and activate a virtual environment at the root of the\nproject (you might need to use `python3` keyword instead of `python`):\n\n```text\npython -m venv .env\nsource .env/bin/activate\n```\n\n(Later you can deactivate the virtual environment with: `deactivate`)\n\nTo build the documentation or run the pre-commit script locally, you need\nto install the development dependencies:\n\n```text\npip install -r dev-requirements.txt\n```\n\n### Documentation\n\nTo build the documentation locally, first change into `docs/` directory:\n\n```text\ncd docs\n```\n\nThen to build the documentation, call:\n\n```text\nmake html\n```\n\nNow you should have a new `docs/build/` directory, and you can open\n`<your-clone-path>/sshkeyboard/docs/build/html/index.html` from your browser.\n\nYou can force the rebuild by running:\n\n```text\nrm -rf build/ && make html\n```\n\nYou can change the documentation content by changing `README.md` or files from\n`src/` or `docs/source/`. If you are mainly changing contents from\n`docs/source/`, you can enable automatic re-building by running:\n\n```text\nsphinx-autobuild ./source/ ./build/html/\n```\n\n### Running the pre-commit script\n\nYou can run the **tests**\n([tox](https://tox.wiki/en/latest/index.html),\n[pytest](https://docs.pytest.org)), **formatting**\n([black](https://black.readthedocs.io/en/stable/),\n[isort](https://pycqa.github.io/isort/)) and **linting**\n([pflake8](https://github.com/csachs/pyproject-flake8),\n[pep8-naming](https://github.com/PyCQA/pep8-naming),\n[codespell](https://github.com/codespell-project/codespell),\n[markdownlint](https://github.com/markdownlint/markdownlint)) simply by\nexecuting:\n\n```text\n./pre-commit\n```\n\nNow if you want to automatically run these when you call `git commit`, copy\nthe script into `.git/hooks/` directory:\n\n```text\ncp pre-commit .git/hooks\n```\n\n> **NOTE**: this process does not run `markdownlint` by default as it\nrequires [Ruby](https://www.ruby-lang.org/en/) to be installed. If you want\nto run `markdownlint` locally as well,\n[install Ruby](https://www.ruby-lang.org/en/documentation/installation/)\nand install markdown lint with `gem install mdl -v 0.11.0`. Then from\n`pre-commit` change `RUN_MDL=false` to `RUN_MDL=true`. (You need to copy the\nfile again into `.git/hooks/` if you did that earlier)\n\n## Comparison to other keyboard libraries\n\nThe other keyboard libraries work by reading proper keycodes from the system.\n\nThis means that they usually require either\n[X server](https://en.wikipedia.org/wiki/X_Window_System) or\n[uinput](https://www.kernel.org/doc/html/v4.12/input/uinput.html), so they do\nnot work over SSH. But this means they do not have the same\n[limitations](#how-it-works) as this library.\n\nThey usually can also support more features such as pressing the keys instead\nof just reacting to user input.\n\nI have good experiences from these libraries:\n\n- [pynput](https://pynput.readthedocs.io/en/latest/)\n- [keyboard](https://github.com/boppreh/keyboard) (requires sudo)",
"bugtrack_url": null,
"license": "",
"summary": "sshkeyboard",
"version": "2.3.1",
"project_urls": {
"Home": "https://github.com/ollipal/sshkeyboard"
},
"split_keywords": [
"keyboard",
"listener",
"ssh",
"callback",
"headless",
"wsl",
"wsl2",
"server",
"x server",
"uinput",
"raspberry pi",
"pynput"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "eab6d24c6184348a91386e5ca5fe3e46668ef978dd694ff98574ef0dd5904522",
"md5": "10020d2b1e422de5f5cae6e1c0f598bd",
"sha256": "05ec2cb116bd9c4a7c17d7add4a5af74e12eb2add24c110608cb90f6812692f9"
},
"downloads": -1,
"filename": "sshkeyboard-2.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "10020d2b1e422de5f5cae6e1c0f598bd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 12870,
"upload_time": "2021-10-28T11:37:05",
"upload_time_iso_8601": "2021-10-28T11:37:05.566970Z",
"url": "https://files.pythonhosted.org/packages/ea/b6/d24c6184348a91386e5ca5fe3e46668ef978dd694ff98574ef0dd5904522/sshkeyboard-2.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "e37bd78e6ade4bb4680d0a610ed02047c4de04db62de8864193bf842c59c47cb",
"md5": "f8c08df60ce10776b03130e81a71370c",
"sha256": "3273be5b2fde7f8d2ea075d40e1981104ac0928d7b77a848756f08d0e66a3d9f"
},
"downloads": -1,
"filename": "sshkeyboard-2.3.1.tar.gz",
"has_sig": false,
"md5_digest": "f8c08df60ce10776b03130e81a71370c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 20296,
"upload_time": "2021-10-28T11:37:08",
"upload_time_iso_8601": "2021-10-28T11:37:08.070495Z",
"url": "https://files.pythonhosted.org/packages/e3/7b/d78e6ade4bb4680d0a610ed02047c4de04db62de8864193bf842c59c47cb/sshkeyboard-2.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2021-10-28 11:37:08",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ollipal",
"github_project": "sshkeyboard",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "sshkeyboard"
}