sshkeyboard
The only keyboard event callback library that works everywhere, even when
used through an SSH 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 and
sequential/concurrent callback modes. For Python 3.6+.
Documentation -
Github source -
PyPI -
Reference -
Quick start
Installation:
pip install sshkeyboard
Simple example to fire events when a key is pressed or released.
esc
key ends listening by default:
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:
$ python example.py
'a' pressed
'a' released
How it works
The sshkeyboard library works without
X server
and uinput.
On Unix based systems (such as Linux, macOS) it works by parsing characters
from sys.stdin. This
is done with fcntl and
termios standard library
modules.
On Windows msvcrt standard
library module is used to read user input. The Windows support is still new,
so please create an issue
if you run into problems.
This behaviour allows it to work where other libraries like
pynput or
keyboard do not work, but
it comes with some limitations, mainly:
- 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.
- 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:
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:
'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
:
listen_keyboard(
on_press=press,
sequential=True,
)
Then pressing "a"
, "s"
and "d"
keys will log:
'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
:
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:
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:
'a' pressed
'a' relased
's' pressed
's' relased
'a' press slept
's' press slept
'a' release slept
's' release slept
And with sequential=True
:
listen_keyboard(
on_press=press,
on_release=release,
sequential=True,
)
will log:
'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"
:
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:
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:
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:
listen_keyboard(
on_press=press,
delay_second_char=0.75,
delay_other_chars=0.05,
)
More
Check out the full
reference
for more functions and parameters such as:
lower
parametersleep
parametermax_thread_pool_workers
parameterlisten_keyboard_manual
function
Direct links to functions:
Development
This sections explains how to build the documentation and how to run the
pre-commit script
locally. This helps if you want to create
a pull request
or if you just want to try things out.
Building the documentations allows you to build all of the files served on the
documentation site locally.
The
pre-commit script
handles running tests, formatting and
linting before each Git commit. These same checks also run automatically on
Github Actions.
Start by cloning this library, and change directory to the project root:
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
):
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:
pip install -r dev-requirements.txt
Documentation
To build the documentation locally, first change into docs/
directory:
cd docs
Then to build the documentation, call:
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:
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:
sphinx-autobuild ./source/ ./build/html/
Running the pre-commit script
You can run the tests
(tox,
pytest), formatting
(black,
isort) and linting
(pflake8,
pep8-naming,
codespell,
markdownlint) simply by
executing:
./pre-commit
Now if you want to automatically run these when you call git commit
, copy
the script into .git/hooks/
directory:
cp pre-commit .git/hooks
NOTE: this process does not run markdownlint
by default as it
requires Ruby to be installed. If you want
to run markdownlint
locally as well,
install Ruby
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 or
uinput, so they do
not work over SSH. But this means they do not have the same
limitations 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: