Detect if modal operator is running

Anyone know of a way to detect if a modal operator is running? Blender definitely has some sort of internal mechanism for it, because if there is a pending autosave it will be postponed until the modal operator is finished or cancelled. I have similar needs, where I have a process running on depsgraph_update_post that needs to be postponed until there are no modal operators running.

Note that I am not talking about context.active_operator. I’m looking to know if a modal operator is currently running, not the last one that was completed.

Any help is appreciated!

The internal handler code is fragile. Exposing this to rna could be a potential nightmare.

This uses ctypes to see if there are any modal operators running. Handles are stored per window, so to check everywhere you’d need to loop over Tested on 2.83, might be wrong offsets for earlier versions. To find the actual operator is probably possible, but would add to the boilerplate. This, even though just for finding a handle is stretching it.

Edit: Fixed :stuck_out_tongue:

import bpy
from ctypes import *

# Handler type enum. Operator is 3

# Generate listbase of appropriate type. None: generic
def listbase(type_=None):
    ptr = POINTER(type_)
    fields = ("first", ptr), ("last", ptr)
    return type("ListBase", (Structure,), {'_fields_': fields})

# Mini struct for Op handlers. *not* bContext!
class OpContext(Structure):
class wmEventHandler(Structure):  # Generic
class wmEventHandler_Op(Structure):  # Operator
class wmWindow(Structure):

wmEventHandler._fields_ = (
    ("next", POINTER(wmEventHandler)),
    ("prev", POINTER(wmEventHandler)),
    ("type", c_int),  # Enum
    ("flag", c_char),
    ("poll", c_void_p),
wmWindow._fields_ = (  # from DNA_windowmanager_types.h
    ("next", POINTER(wmWindow)),
    ("prev", POINTER(wmWindow)),
    ("ghostwin", c_void_p),
    ("gpuctx", c_void_p),
    ("parent", POINTER(wmWindow)),
    ("scene", c_void_p),
    ("new_scene", c_void_p),
    ("view_layer_name", c_char * 64),
    ("workspace_hook", c_void_p),
    ("global_areas", listbase(type_=None) * 3),
    ("screen", c_void_p),
    ("posx", c_short),
    ("posy", c_short),
    ("sizex", c_short),
    ("sizey", c_short),
    ("windowstate", c_short),
    ("monitor", c_short),
    ("active", c_short),
    ("cursor", c_short),
    ("lastcursor", c_short),
    ("modalcursor", c_short),
    ("grabcursor", c_short),
    ("addmousemove", c_short),
    ("winid", c_int),
    ("lock_pie_event", c_short),
    ("last_pie_event", c_short),
    ("eventstate", c_void_p),
    ("tweak", c_void_p),
    ("ime_data", c_void_p),
    ("queue", listbase(type_=None)),
    ("handlers", listbase(type_=None)),
    ("modalhandlers", listbase(type_=wmEventHandler)),
    ("gesture", listbase(type_=None)),
    ("stereo3d_format", c_void_p),
    ("drawcalls", listbase(type_=None)),
    ("cursor_keymap_status", c_void_p)
OpContext._fields_ = (
    ("win", POINTER(wmWindow)),
    ("area", c_void_p),  # <-- ScrArea ptr
    ("region", c_void_p),  # <-- ARegion ptr
    ("region_type", c_short)
wmEventHandler_Op._fields_ = (
    ("head", wmEventHandler),
    ("op", c_void_p), # <-- wmOperator
    ("is_file_select", c_bool),
    ("context", OpContext)

if __name__ == "__main__":
    window = bpy.context.window
    win = cast(window.as_pointer(), POINTER(wmWindow)).contents

    handle = win.modalhandlers.first
    while handle:
        if handle.contents.type == WM_HANDLER_TYPE_OP:
            print("Modal running")
        handle =
        print("No running modals")


Very clever! I wasn’t expecting it to be simple, I’m just glad there’s a way. Thanks!

This is a tricky one to understand. Any chance you could offer some help on how to only detect modal grab?


I dont think there’s a easy way to detect if an operator is active without accessing blenders process memory.

But maybe its enough to detect if an object has been moved?

That’s a really good thought. I’m already detecting movement. Not sure why it didn’t occur to me.