#!/usr/bin/env python3 # Copyright (C) 2026 Nicolás Ortega Froysa All rights reserved. # Author: Nicolás Ortega Froysa # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # # 3. This notice may not be removed or altered from any source # distribution. """ Keyboard Input Blocker for X11/Wayland A graphical application that toggles keyboard input on/off. Requires: PyQt6, evdev """ import sys import os from pathlib import Path from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, QMessageBox from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QFont import evdev from evdev import InputDevice, list_devices class KeyboardBlockerThread(QThread): """Thread to handle keyboard grabbing in the background.""" error_occurred = pyqtSignal(str) grabbed = pyqtSignal() released = pyqtSignal() def __init__(self): super().__init__() self.keyboard_device = None self.is_blocking = False self.should_stop = False def find_keyboard_device(self): """Find the keyboard input device.""" devices = [InputDevice(path) for path in list_devices()] # Look for keyboard devices keyboard_devices = [] for device in devices: try: caps = device.capabilities() # Check if device has KEY capability (keyboards have this) if evdev.ecodes.EV_KEY in caps: # Prefer devices with common keyboard names if any(name in device.name.lower() for name in ['keyboard', 'input', 'wacom']): keyboard_devices.append(device) except: continue if keyboard_devices: return keyboard_devices[0] # Fallback: return first device with KEY capability for device in devices: try: caps = device.capabilities() if evdev.ecodes.EV_KEY in caps: return device except: continue return None def grab_keyboard(self): """Grab the keyboard device to block input.""" try: self.keyboard_device = self.find_keyboard_device() if not self.keyboard_device: self.error_occurred.emit( "No keyboard device found. You may need to run with sudo." ) return False # Grab the device (this requires appropriate permissions) self.keyboard_device.grab() self.is_blocking = True self.grabbed.emit() return True except PermissionError: self.error_occurred.emit( "Permission denied. Please run with elevated privileges (sudo)." ) return False except Exception as e: self.error_occurred.emit(f"Error grabbing keyboard: {str(e)}") return False def release_keyboard(self): """Release the keyboard device to allow input.""" try: if self.keyboard_device: self.keyboard_device.ungrab() self.is_blocking = False self.released.emit() return True except Exception as e: self.error_occurred.emit(f"Error releasing keyboard: {str(e)}") return False class KeyboardBlockerApp(QMainWindow): """Main application window for keyboard blocker.""" def __init__(self): super().__init__() self.blocker_thread = KeyboardBlockerThread() self.is_keyboard_blocked = False # Connect thread signals self.blocker_thread.grabbed.connect(self.on_keyboard_grabbed) self.blocker_thread.released.connect(self.on_keyboard_released) self.blocker_thread.error_occurred.connect(self.on_error) self.init_ui() def init_ui(self): """Initialize the user interface.""" self.setWindowTitle("Keyboard Input Blocker") self.setGeometry(100, 100, 400, 200) # Create central widget and layout central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # Title label title_label = QLabel("Keyboard Input Blocker") title_font = QFont() title_font.setPointSize(14) title_font.setBold(True) title_label.setFont(title_font) layout.addWidget(title_label) # Status label self.status_label = QLabel("Status: Keyboard enabled") layout.addWidget(self.status_label) # Toggle button self.toggle_button = QPushButton("Block Keyboard") self.toggle_button.setMinimumHeight(50) self.toggle_button.setFont(QFont("Times", 12)) self.toggle_button.clicked.connect(self.toggle_keyboard) layout.addWidget(self.toggle_button) # Info label info_label = QLabel( "Click the button to toggle keyboard input blocking.\n" "Note: This application may require elevated privileges (sudo)." ) info_font = QFont() info_font.setPointSize(9) info_label.setFont(info_font) layout.addWidget(info_label) layout.addStretch() def toggle_keyboard(self): """Toggle keyboard blocking on/off.""" if self.is_keyboard_blocked: self.blocker_thread.release_keyboard() else: self.blocker_thread.grab_keyboard() def on_keyboard_grabbed(self): """Handle keyboard grabbed signal.""" self.is_keyboard_blocked = True self.toggle_button.setText("Unblock Keyboard") self.toggle_button.setStyleSheet("background-color: #ff6b6b;") self.status_label.setText("Status: Keyboard BLOCKED ⚠") def on_keyboard_released(self): """Handle keyboard released signal.""" self.is_keyboard_blocked = False self.toggle_button.setText("Block Keyboard") self.toggle_button.setStyleSheet("") self.status_label.setText("Status: Keyboard enabled ✓") def on_error(self, message): """Handle error signal.""" QMessageBox.critical(self, "Error", message) def closeEvent(self, a0): """Ensure keyboard is released when closing.""" if self.is_keyboard_blocked: self.blocker_thread.release_keyboard() self.blocker_thread.quit() self.blocker_thread.wait() a0.accept() def main(): """Main entry point.""" app = QApplication(sys.argv) window = KeyboardBlockerApp() window.show() sys.exit(app.exec()) if __name__ == "__main__": main()