Ren'Py Disabling the custom mouse implementation in some Ren'Py games and setting it to the default method

Enlight432

Active Member
Jan 4, 2024
810
1,335
I've been working on this issue a lot, but I haven't been able to solve it
I wrote a Python code that hides the mouse cursor after 2 seconds, but some Ren'Py developers use custom cursors that this code can't target, so the cursor don't disappear
Example: game A Wife and Mother
Any help would be appreciated
I've attached the code

Python:
import ctypes
import time
from threading import Timer
import atexit

# Access to Windows functions
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32

# Constants for system cursor types
SYSTEM_CURSORS = [
    32512,  # IDC_ARROW
    32513,  # IDC_IBEAM
    32514,  # IDC_WAIT
    32515,  # IDC_CROSS
    32516,  # IDC_UPARROW
    32642,  # IDC_SIZENWSE
    32643,  # IDC_SIZENESW
    32644,  # IDC_SIZEWE
    32645,  # IDC_SIZENS
    32646,  # IDC_SIZEALL
    32648,  # IDC_NO
    32649,  # IDC_HAND
    32650,  # IDC_APPSTARTING
]

# Constant for SystemParametersInfo
SPI_SETCURSORS = 0x0057

# Dictionary for storing cursor sets
cursors = {}

# Variable for tracking cursor status
visible = True
hidden = False

# Variables related to mouse position
last_mouse_pos = None
last_move_time = time.time()

# Timer for checking mouse
mouse_timer = None
running = False

def system_cursor(cmd):
    """Change cursor display status"""
    global visible, cursors
   
    # Load cursors if they haven't been loaded yet or if a reload is requested
    if cmd == "Reload" or not cursors:
        # Load and save default and blank cursors
        for cursor_id in SYSTEM_CURSORS:
            # Load default cursor
            h_cursor = user32.LoadCursorW(0, cursor_id)
            h_default = user32.CopyImage(h_cursor, 2, 0, 0, 0)

            # Create blank cursor (32x32 pixels)
            and_mask = b'\xFF' * (32 * 4)  # AND mask (all 1s)
            xor_mask = b'\x00' * (32 * 4)  # XOR mask (all 0s)
            h_blank = user32.CreateCursor(0, 0, 0, 32, 32, and_mask, xor_mask)

            cursors[cursor_id] = {
                'default': h_default,
                'blank': h_blank
            }

    # Change cursor status
    if cmd == "Show":
        visible = True
    elif cmd == "Hide":
        visible = False
    elif cmd == "Toggle":
        visible = not visible
    else:
        return

    # Apply changes to all system cursors
    for cursor_id, handles in cursors.items():
        h_cursor = user32.CopyImage(
            handles['default'] if visible else handles['blank'],
            2, 0, 0, 0
        )
        try:
            user32.SetSystemCursor(h_cursor, cursor_id)
        except Exception as e:
            # If setting the cursor fails, destroy the copied version
            user32.DestroyCursor(h_cursor)
            print(f"Error setting system cursor: {e}")

def reset_system_cursors():
    """Reset system cursors to default state"""
    try:
        user32.SystemParametersInfoW(SPI_SETCURSORS, 0, None, 0)
    except Exception as e:
        print(f"Error resetting system cursors: {e}")

def get_mouse_position():
    """Get current mouse position"""
    class POINT(ctypes.Structure):
        _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]

    pt = POINT()
    user32.GetCursorPos(ctypes.byref(pt))
    return (pt.x, pt.y)

def check_mouse():
    """Check mouse movement and update cursor display status"""
    global hidden, last_mouse_pos, last_move_time, mouse_timer, running
   
    if not running:
        return
   
    try:
        # Get current mouse position
        current_pos = get_mouse_position()
        current_time = time.time()

        # If mouse position has changed
        if last_mouse_pos is None:
            last_mouse_pos = current_pos
        elif current_pos != last_mouse_pos:
            last_move_time = current_time
            last_mouse_pos = current_pos

        # Calculate mouse idle time
        idle_time = current_time - last_move_time

        # If mouse is idle for more than 2 seconds and cursor is visible
        if idle_time > 2 and not hidden:
            system_cursor("Hide")
            hidden = True
        # If mouse has moved and cursor is hidden
        elif idle_time < 2 and hidden:
            system_cursor("Show")
            hidden = False
    except Exception as e:
        print(f"Error in check_mouse function: {e}")
        # In case of error, ensure cursor is visible
        try:
            reset_system_cursors()
            hidden = False
        except:
            pass
   
    # Schedule next check if program is still running
    if running:
        mouse_timer = Timer(0.1, check_mouse)
        mouse_timer.daemon = True  # Allow timer thread to exit when main program exits
        mouse_timer.start()

def cleanup():
    """Cleanup function to restore cursors on exit"""
    global running, mouse_timer
   
    running = False
   
    # Cancel timer if it exists
    if mouse_timer is not None:
        mouse_timer.cancel()
   
    # Ensure cursor is visible on exit
    try:
        reset_system_cursors()
    except:
        try:
            system_cursor("Show")
        except:
            pass

def main():
    global running
   
    # Register cleanup function for program exit
    atexit.register(cleanup)
   
    # Initial cursor loading
    system_cursor("Reload")
   
    # Start mouse checking
    running = True
    check_mouse()
   
    # Keep program running
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass

if __name__ == "__main__":
    main()
 
Last edited:

Quintillian

Member
Apr 15, 2019
136
252
With what I know of Ren'Py so far, it's usually less painful to work with the engine than against it, so I'm curious what limitations did you find using the way to change the cursor?

EDIT: This was silly. I just learned there is a property that controls this behavior
 
Last edited: