Created
October 30, 2025 04:30
-
-
Save Lvl4Sword/3bb80f5a0cb4967e0997e555f535085c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| from PyQt5.QtWidgets import QApplication, QWidget | |
| from PyQt5.QtCore import Qt, QTimer, QPoint, pyqtSignal, QObject | |
| from PyQt5.QtGui import QPainter, QPen, QColor | |
| from pynput import keyboard, mouse | |
| import sys | |
| class RippleWidget(QWidget): | |
| def __init__(self, x, y): | |
| super().__init__() | |
| self.x = x | |
| self.y = y | |
| self.frame = 0 | |
| self.max_frames = 40 | |
| # Window setup | |
| self.setWindowFlags( | |
| Qt.WindowStaysOnTopHint | | |
| Qt.FramelessWindowHint | | |
| Qt.Tool | | |
| Qt.WindowTransparentForInput | |
| ) | |
| self.setAttribute(Qt.WA_TranslucentBackground) | |
| self.setAttribute(Qt.WA_ShowWithoutActivating) | |
| # Size and position | |
| size = 500 | |
| self.setGeometry(x - size//2, y - size//2, size, size) | |
| self.center = size // 2 | |
| # Animation timer | |
| self.timer = QTimer(self) | |
| self.timer.timeout.connect(self.animate) | |
| self.timer.start(16) # ~60 FPS | |
| self.show() | |
| def paintEvent(self, event): | |
| painter = QPainter(self) | |
| painter.setRenderHint(QPainter.Antialiasing) | |
| progress = self.frame / self.max_frames | |
| # Draw 3 concentric circles | |
| for i in range(3): | |
| delay = i * 0.2 | |
| circle_progress = max(0, min(1, (progress - delay) / (1 - delay))) | |
| if circle_progress > 0: | |
| # Calculate radius and opacity | |
| radius = circle_progress * 200 | |
| opacity = int((1 - circle_progress) * 255) | |
| # Set pen color with fade | |
| pen = QPen(QColor(0, 150, 255, opacity)) | |
| pen.setWidth(4) | |
| painter.setPen(pen) | |
| # Draw circle | |
| painter.drawEllipse( | |
| QPoint(self.center, self.center), | |
| int(radius), int(radius) | |
| ) | |
| def animate(self): | |
| self.frame += 1 | |
| if self.frame >= self.max_frames: | |
| self.timer.stop() | |
| self.close() | |
| else: | |
| self.update() | |
| class RippleEffect(QObject): | |
| ripple_signal = pyqtSignal(int, int) | |
| def __init__(self): | |
| super().__init__() | |
| self.app = QApplication(sys.argv) | |
| self.ripples = [] | |
| # Connect signal to create ripple in main thread | |
| self.ripple_signal.connect(self.create_ripple_slot) | |
| def create_ripple_slot(self, x, y): | |
| """This runs in the main Qt thread""" | |
| ripple = RippleWidget(x, y) | |
| self.ripples.append(ripple) | |
| # Clean up closed ripples | |
| self.ripples = [r for r in self.ripples if r.isVisible()] | |
| def on_key_press(self, key): | |
| """This runs in the pynput thread""" | |
| try: | |
| if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r: | |
| controller = mouse.Controller() | |
| pos = controller.position | |
| # Emit signal to main thread | |
| self.ripple_signal.emit(pos[0], pos[1]) | |
| except: | |
| pass | |
| def start(self): | |
| # Start keyboard listener in separate thread | |
| listener = keyboard.Listener(on_press=self.on_key_press) | |
| listener.start() | |
| print("Mouse ripple effect active!") | |
| print("Press LEFT or RIGHT CTRL to show ripple") | |
| print("Press Ctrl+C to exit") | |
| sys.exit(self.app.exec_()) | |
| if __name__ == "__main__": | |
| effect = RippleEffect() | |
| effect.start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment