Skip to content

Instantly share code, notes, and snippets.

@Lvl4Sword
Created October 30, 2025 04:30
Show Gist options
  • Save Lvl4Sword/3bb80f5a0cb4967e0997e555f535085c to your computer and use it in GitHub Desktop.
Save Lvl4Sword/3bb80f5a0cb4967e0997e555f535085c to your computer and use it in GitHub Desktop.
#!/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