A simple Python module for listening to keyboard keybinds (single keys or combinations) with toggle, double-press, and hold support.
Project description
PyKeybindManager
A simple Python module for listening to specific keyboard keybinds (single keys or combinations) using the pynput library. Supports toggle, double-press toggle, and press-and-hold activation types, suitable for applications like dictation control. Includes optional sound feedback.
Features
- Listen for specific keyboard keys (e.g.,
F1,fn) or combinations (e.g.,Ctrl+C,Cmd+Shift+P). - Supports multiple activation modes via
trigger_type:'toggle': Activate on each press of the key/combination.'double_press_toggle': Activate differently for single vs. double presses (single keys only).'hold': Activate on press and again on release of the key/combination.
- Run the listener in a background thread.
- Trigger a user-defined callback function with the event type (
'press','release','single','double'). - Provide optional sound feedback ('start'/'stop' sounds) using platform-specific methods.
- Helper function (
parse_keybind_string) to easily convert keybind strings (e.g.,"alt+t","f1") into the required internal format. - Handles macOS-specific considerations like Input Monitoring permissions and the 'fn' key.
- Minimal logging by default; relies on the application to configure logging levels.
Requirements
- Python 3.x
pynputlibrary (pip install pynput)
Platform Notes:
- macOS: Requires "Input Monitoring" permission for the application/terminal running the script. The script attempts to handle the
OBJC_DISABLE_INITIALIZE_FORK_SAFETYenvironment variable needed bypynputon macOS. Thefnkey is supported.cmdkey is mapped correctly. - Linux: May require root privileges depending on the environment, or the user needs to be in the
inputgroup.metakey is mapped toctrl. - Windows: Should generally work without special permissions.
metakey is mapped toctrl.- Sound Playback: Relies on common system utilities (
afplayon macOS,winsoundon Windows,aplay/paplay/mplayer/mpg123on Linux). If these are not available, sound playback might fail silently or log a warning.
- Sound Playback: Relies on common system utilities (
Installation
Install the package from PyPI using pip:
pip install pykeybindmanager
This will also automatically install the required pynput dependency.
Development Installation
If you have cloned the repository and want to install it for development (e.g., to make changes):
# Navigate to the repository root directory (where pyproject.toml is)
pip install -e .
Usage
import time
import logging
import sys
from pykeybindmanager import KeybindManager, parse_keybind_string, play_sound_file
from pykeybindmanager.exceptions import PermissionError, PynputImportError, InvalidKeybindError, ListenerError
# --- Application State (Example for Dictation) ---
is_recording = False
# --- Configure Logging (Optional) ---
# The library uses NullHandler by default. Configure if you want to see logs.
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
log = logging.getLogger(__name__) # Get logger for application messages
# --- Callback Functions ---
def handle_toggle_activation(event_type):
"""Handles 'toggle' type activation (e.g., press Ctrl+D to start/stop)."""
global is_recording
if event_type == 'press':
is_recording = not is_recording
status = "STARTED" if is_recording else "STOPPED"
sound = 'start' if is_recording else 'stop'
play_sound_file(sound)
log.info(f"Toggle Keybind Pressed: Recording {status}")
def handle_double_press_activation(event_type):
"""Handles 'double_press_toggle' type activation (e.g., double-press F1 to start/stop)."""
global is_recording
# Example: Only toggle on double press
if event_type == 'double':
is_recording = not is_recording
status = "STARTED" if is_recording else "STOPPED"
sound = 'start' if is_recording else 'stop'
play_sound_file(sound)
log.info(f"Double Press Detected: Recording {status}")
elif event_type == 'single':
log.info("Single press detected (ignored by this handler).")
def handle_hold_activation(event_type):
"""Handles 'hold' type activation (e.g., hold 'fn' to record)."""
global is_recording
if event_type == 'press':
if not is_recording:
is_recording = True
play_sound_file('start')
log.info("Hold Key Pressed: Recording STARTED")
elif event_type == 'release':
if is_recording:
is_recording = False
play_sound_file('stop')
log.info("Hold Key Released: Recording STOPPED")
def handle_error(exception):
"""Handles errors from the KeybindManager."""
log.error(f"KeybindManager Error: {type(exception).__name__} - {exception}")
if isinstance(exception, PermissionError):
log.error("Please ensure the application has Input Monitoring permissions (macOS) or necessary privileges.")
# Consider exiting or notifying the user based on the error
# --- Main Logic ---
if __name__ == "__main__":
managers = []
try:
# --- Define Keybinds ---
# Example 1: Toggle recording with Ctrl+D
kb1_str = "ctrl+d"
kb1_def = parse_keybind_string(kb1_str)
manager1 = KeybindManager(kb1_def, handle_toggle_activation, trigger_type='toggle', on_error=handle_error)
managers.append(manager1)
log.info(f"Registered '{kb1_str}' with trigger 'toggle'")
# Example 2: Double-press F1 to toggle recording
kb2_str = "f1"
kb2_def = parse_keybind_string(kb2_str)
manager2 = KeybindManager(kb2_def, handle_double_press_activation, trigger_type='double_press_toggle', on_error=handle_error)
managers.append(manager2)
log.info(f"Registered '{kb2_str}' with trigger 'double_press_toggle'")
# Example 3: Hold 'fn' key to record (macOS specific)
if sys.platform == 'darwin':
kb3_str = "fn"
try:
kb3_def = parse_keybind_string(kb3_str)
manager3 = KeybindManager(kb3_def, handle_hold_activation, trigger_type='hold', on_error=handle_error)
managers.append(manager3)
log.info(f"Registered '{kb3_str}' with trigger 'hold'")
except InvalidKeybindError as e:
log.warning(f"Could not register 'fn' key: {e}") # Might fail if pynput doesn't map vk 179
# --- Start Listeners ---
log.info("Starting listeners... Press Ctrl+C to exit.")
for manager in managers:
manager.start_listener()
# Keep the main script running
while True:
time.sleep(1)
except (PynputImportError, InvalidKeybindError, ValueError, ListenerError) as e:
log.error(f"Initialization Error: {e}")
except KeyboardInterrupt:
log.info("KeyboardInterrupt received. Stopping listeners...")
except Exception as e:
log.error(f"An unexpected error occurred: {e}", exc_info=True)
finally:
# --- Stop Listeners Gracefully ---
for manager in managers:
manager.stop_listener()
log.info("All listeners stopped.")
Key Concepts (v0.2.2)
parse_keybind_string(keybind_string):- Input: A string like
"f1","ctrl+c","alt+shift+t","fn". Uses+as a separator. - Output: A tuple
(frozenset[modifier_keys], main_key). Modifiers arepynput.keyboard.Keyobjects (e.g.,Key.ctrl). The main key is aKeyorKeyCodeobject.
- Input: A string like
KeybindManager(keybind_definition, on_activated, trigger_type, on_error=None, double_press_threshold=0.3):keybind_definition: The tuple returned byparse_keybind_string.on_activated: Your callback function. Receives one argument: the event type string ('press','release','single', or'double').trigger_type: Specifies the activation behavior. Must be one of:'toggle': Callback gets'press'on activation. Good for start/stop actions triggered by the same key/combo.'double_press_toggle': Callback gets'single'or'double'. Only valid for single keys (no modifiers). Good for distinguishing single vs. double taps.'hold': Callback gets'press'when the key/combo goes down, and'release'when the main key comes up. Ideal for push-to-talk/record.
on_error: Optional function to handle errors (like permission issues). Receives the exception object.double_press_threshold: Time in seconds for'double_press_toggle'detection (default: 0.3s).
manager.start_listener(): Starts listening in the background.manager.stop_listener(): Stops the background listener thread.play_sound_file(sound_type, blocking=False): Plays a sound.sound_type: Either'start'(playsdoubleping.wav) or'stop'(playssingleping.wav).blocking: IfTrue, waits for the sound to finish. Defaults toFalse.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykeybindmanager-0.2.2.tar.gz.
File metadata
- Download URL: pykeybindmanager-0.2.2.tar.gz
- Upload date:
- Size: 282.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8093039d43df41140ed779011eec1fb2c1b9882f0a5562feaf18b698e8f1327f
|
|
| MD5 |
09cc5d89e7783192ad545250bfc8ef5a
|
|
| BLAKE2b-256 |
0cb84304c74ca1b83399f581957f03fa1b5de0b84b92e368a7f599df382b3a0f
|
File details
Details for the file pykeybindmanager-0.2.2-py3-none-any.whl.
File metadata
- Download URL: pykeybindmanager-0.2.2-py3-none-any.whl
- Upload date:
- Size: 278.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
733afc15fdc9e501e328507eb62fc7e19f3b90b9e5e226c61fd10fc538006f86
|
|
| MD5 |
0593c0b91d7ce35dab483584ab972e80
|
|
| BLAKE2b-256 |
c32022a700a57cafd95af5349ed08e39834aa7f1b0b503b3841c966fc47ee68c
|