Initial commit.

This commit is contained in:
2026-07-01 07:19:52 +02:00
commit b870b7a165
4 changed files with 362 additions and 0 deletions
Executable
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env python3
# Copyright (C) 2026 Nicolás Ortega Froysa <nicolas@ortegas.org> All rights reserved.
# Author: Nicolás Ortega Froysa <nicolas@ortegas.org>
#
# 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()