Code for How to Control your Keyboard in Python
Want the advanced version? Keyboard Automation Pro is the complete tool built from this tutorial,
with Text expander, Placeholders, Transform selected text and more.
View on Github
keyboard_controller_2026.py
from __future__ import annotations
import argparse
import platform
import sys
import time
from dataclasses import dataclass
from typing import Callable
@dataclass(frozen=True)
class Action:
"""A keyboard action that can be previewed or executed."""
description: str
callback: Callable[[object], None]
def import_keyboard():
"""Import keyboard lazily so --dry-run works in headless/test environments."""
try:
import keyboard
except ImportError as exc:
raise SystemExit("Install the dependency first: pip install keyboard") from exc
return keyboard
def platform_hint() -> str:
"""Return a short note about common platform requirements."""
system = platform.system().lower()
if system == "linux":
return "Linux usually needs root privileges or access to /dev/input for global hooks."
if system == "darwin":
return "macOS support in the keyboard package is limited; pynput or PyAutoGUI is often a better fit."
if system == "windows":
return "Windows is the smoothest platform for the keyboard package."
return "Global keyboard hooks depend on your operating system and permissions."
def preview_or_run(action: Action, keyboard_module: object | None, execute: bool) -> None:
"""Print what would happen, or run the action when --execute is set."""
if not execute:
print(f"DRY RUN: {action.description}")
return
if keyboard_module is None:
raise RuntimeError("keyboard module is required when execute=True")
print(f"RUNNING: {action.description}")
action.callback(keyboard_module)
def demo_typing(execute: bool) -> None:
"""Type text into the currently focused window."""
keyboard = import_keyboard() if execute else None
actions = [
Action(
"type 'Hello from Python!' with a small delay",
lambda kb: kb.write("Hello from Python!", delay=0.03),
),
Action("press Enter", lambda kb: kb.press_and_release("enter")),
Action("send Ctrl+A", lambda kb: kb.send("ctrl+a")),
]
for action in actions:
preview_or_run(action, keyboard, execute)
def demo_hotkeys(execute: bool) -> None:
"""Register a global hotkey and an abbreviation."""
keyboard = import_keyboard() if execute else None
if not execute:
print("DRY RUN: register Ctrl+Alt+H to print a message")
print("DRY RUN: expand '@email' into 'name@example.com'")
print("DRY RUN: wait until Esc is pressed")
return
keyboard.add_hotkey("ctrl+alt+h", lambda: print("Ctrl+Alt+H pressed"))
keyboard.add_abbreviation("@email", "name@example.com")
print("Hotkeys are active. Type @email + space in another app, or press Ctrl+Alt+H.")
print("Press Esc to stop.")
keyboard.wait("esc")
keyboard.unhook_all()
def demo_record(execute: bool) -> None:
"""Record keyboard events until Esc, then replay them after confirmation."""
keyboard = import_keyboard() if execute else None
if not execute:
print("DRY RUN: record keyboard events until Esc")
print("DRY RUN: show typed strings")
print("DRY RUN: ask before replaying events")
return
print("Recording now. Press Esc to stop.")
events = keyboard.record("esc")
typed = list(keyboard.get_typed_strings(events))
print("Typed strings:", typed)
answer = input("Replay the recorded events? [y/N] ").strip().lower()
if answer == "y":
print("Replaying in 3 seconds. Focus the target window now.")
time.sleep(3)
keyboard.play(events)
keyboard.unhook_all()
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Safe examples for controlling the keyboard in Python.")
parser.add_argument(
"demo",
choices=("typing", "hotkeys", "record"),
help="Which demo to run",
)
parser.add_argument(
"--execute",
action="store_true",
help="Actually send/listen to keyboard events. Without this flag, the script only prints what it would do.",
)
args = parser.parse_args(argv)
print(platform_hint())
if not args.execute:
print("Running in dry-run mode. Add --execute on your own desktop to perform the actions.\n")
if args.demo == "typing":
demo_typing(args.execute)
elif args.demo == "hotkeys":
demo_hotkeys(args.execute)
elif args.demo == "record":
demo_record(args.execute)
return 0
if __name__ == "__main__":
raise SystemExit(main())