Mouse Jiggler for Zorin OS

I was so stunned that there was not a single Mouse Jiggler GUI in Linux. Coming from Windows 11, that was one of the first things I started to look for and I found none. I installed Shell Extension like Keep Awake, Caffeine, etc. but none serve any purpose for my use case. All I needed was something that would jiggle my mouse cursor in certain interval and keep my windows alive. So I coded one for myself, took me approx. 5-6 hours to code the whole damn thing but it's well worth.

Sharing code if anyone looking for same tool. It is written in a python, first it was 3 lines code that ran from terminal but decided to give GUI look that took helluva time. Anyway, save it in .py format, you can change the title bar name to whatever name you like, run it or compile it. Good Luck.

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk, messagebox
import subprocess
import signal
import os
import sys
import threading
import time
import re
import glob


def find_mouse_device():
    """
    Find mouse device by reading input device information from /proc/bus/input/devices
    and matching it with corresponding event in /dev/input/
    """
    try:
        # Read the input devices file
        with open('/proc/bus/input/devices', 'r') as f:
            content = f.read()

        # Split into device blocks
        devices = content.split('\n\n')
        
        # Look for mouse devices
        for device in devices:
            if 'mouse' in device.lower():
                # Get the event handler number
                handlers = re.search(r'H: Handlers=.*event(\d+)', device)
                if handlers:
                    event_num = handlers.group(1)
                    device_path = f'/dev/input/event{event_num}'
                    
                    # Verify the device exists
                    if os.path.exists(device_path):
                        # Get device name for the message
                        name_match = re.search(r'N: Name="([^"]+)"', device)
                        device_name = name_match.group(1) if name_match else "Mouse device"
                        
                        return device_path, device_name
        
        return None, None

    except Exception as e:
        print(f"Error finding mouse device: {e}")
        return None, None

class MouseJigglerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Navin's Mouse Jiggler")
        self.root.geometry("500x500")
        self.jiggler_process = None
        self.is_running = False
        
        # Main frame
        main_frame = ttk.Frame(root, padding="20")
        main_frame.pack(expand=True, fill='both')
        
        # Auto-detect mouse device
        device_path, device_name = find_mouse_device()
        default_device = device_path if device_path else ""
        
        # Device section
        device_label_frame = ttk.LabelFrame(main_frame, text="Device Settings", padding="10")
        device_label_frame.pack(fill='x', pady=(0, 15))
        
        # Device path label and entry
        ttk.Label(device_label_frame, text="Mouse Device Path:").pack(anchor='w')
        self.device_var = tk.StringVar(value=default_device)
        self.device_entry = ttk.Entry(device_label_frame, textvariable=self.device_var, width=40)
        self.device_entry.pack(fill='x', pady=(5, 10))
        
        # Detect button in its own frame
        detect_frame = ttk.Frame(device_label_frame)
        detect_frame.pack(fill='x')
        
        self.detect_button = tk.Button(
            detect_frame,
            text="Detect Mouse Device",
            command=self.detect_mouse,
            width=20,
            height=1,
            bg='#ADD8E6',  # Light blue
            relief=tk.RAISED
        )
        self.detect_button.pack(pady=5)
        
        # Movement settings section
        settings_label_frame = ttk.LabelFrame(main_frame, text="Movement Settings", padding="10")
        settings_label_frame.pack(fill='x', pady=(0, 15))
        
        # Interval settings
        ttk.Label(settings_label_frame, text="Interval (seconds):").pack(anchor='w')
        self.interval_var = tk.StringVar(value="30")
        self.interval_entry = ttk.Entry(settings_label_frame, textvariable=self.interval_var, width=10)
        self.interval_entry.pack(pady=(5, 10))
        
        # Distance settings
        ttk.Label(settings_label_frame, text="Distance (pixels):").pack(anchor='w')
        self.distance_var = tk.StringVar(value="10")
        self.distance_entry = ttk.Entry(settings_label_frame, textvariable=self.distance_var, width=10)
        self.distance_entry.pack(pady=(5, 10))
        
        # Status section
        status_frame = ttk.LabelFrame(main_frame, text="Status", padding="10")
        status_frame.pack(fill='x', pady=(0, 15))
        
        self.status_var = tk.StringVar(value="Status: Stopped")
        self.status_label = ttk.Label(status_frame, textvariable=self.status_var)
        self.status_label.pack(pady=5)
        
        # Control buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(pady=10)
        
        self.start_button = tk.Button(
            button_frame, 
            text="Start Jiggler",
            command=self.start_jiggler,
            width=15,
            height=2,
            bg='#90EE90',  # Light green
            relief=tk.RAISED
        )
        self.start_button.pack(side=tk.LEFT, padx=10)
        
        self.stop_button = tk.Button(
            button_frame, 
            text="Stop Jiggler",
            command=self.stop_jiggler,
            width=15,
            height=2,
            bg='#FFB6C1',  # Light red
            relief=tk.RAISED,
            state=tk.DISABLED
        )
        self.stop_button.pack(side=tk.LEFT, padx=10)
        
        # Initial detection if no device found
        if not default_device:
            self.root.after(1000, self.detect_mouse)  # Try to detect after GUI is shown
        
        # Bind window close event
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def detect_mouse(self):
        device_path, device_name = find_mouse_device()
        if device_path:
            self.device_var.set(device_path)
            messagebox.showinfo("Success", f"Mouse device found:\n{device_name}\nPath: {device_path}")
        else:
            messagebox.showerror("Error", "No mouse device found. Please ensure your mouse is connected and you have the necessary permissions.")
        
    def validate_inputs(self):
        try:
            interval = float(self.interval_var.get())
            distance = int(self.distance_var.get())
            if interval <= 0 or distance <= 0:
                raise ValueError
            return True
        except ValueError:
            messagebox.showerror("Invalid Input", "Please enter valid numbers for interval and distance")
            return False
            
    def jiggler_loop(self):
        while self.is_running:
            try:
                device = self.device_var.get()
                interval = float(self.interval_var.get())
                distance = self.distance_var.get()
                
                # Move mouse right
                subprocess.run([
                    "/usr/bin/evemu-event",
                    device,
                    "--type", "EV_REL",
                    "--code", "REL_X",
                    "--value", distance,
                    "--sync"
                ], check=True)
                
                # Move mouse left
                subprocess.run([
                    "/usr/bin/evemu-event",
                    device,
                    "--type", "EV_REL",
                    "--code", "REL_X",
                    "--value", f"-{distance}",
                    "--sync"
                ], check=True)
                
                time.sleep(interval)
                
            except subprocess.CalledProcessError:
                self.is_running = False
                self.root.after(0, self.handle_error)
                break
                
    def handle_error(self):
        messagebox.showerror("Error", f"Failed to move mouse. Check if device path is correct and you have necessary permissions.")
        self.stop_jiggler()
        
    def start_jiggler(self):
        if not self.validate_inputs():
            return
            
        if not os.path.exists(self.device_var.get()):
            messagebox.showerror("Error", f"Device {self.device_var.get()} not found")
            return
            
        self.is_running = True
        self.status_var.set("Status: Running")
        self.start_button.configure(state=tk.DISABLED)
        self.stop_button.configure(state=tk.NORMAL)
        
        # Start the jiggler in a separate thread
        self.jiggler_thread = threading.Thread(target=self.jiggler_loop, daemon=True)
        self.jiggler_thread.start()
        
    def stop_jiggler(self):
        self.is_running = False
        self.status_var.set("Status: Stopped")
        self.start_button.configure(state=tk.NORMAL)
        self.stop_button.configure(state=tk.DISABLED)
        
    def on_closing(self):
        if self.is_running:
            self.stop_jiggler()
        self.root.destroy()
        sys.exit(0)

if __name__ == "__main__":
    root = tk.Tk()
    app = MouseJigglerGUI(root)
    root.mainloop()

4 Likes

You should really put it up at github, so people can share, evolve and/or contribute to it :slight_smile:

3 Likes

Right in the spirit of open source, thanks for sharing! :smiley:

3 Likes