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 context.window_manager.windows. 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
WM_HANDLER_TYPE_GIZMO = 1
WM_HANDLER_TYPE_UI = 2
WM_HANDLER_TYPE_OP = 3
WM_HANDLER_TYPE_DROPBOX = 4
WM_HANDLER_TYPE_KEYMAP = 5

# 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):
    pass
class wmEventHandler(Structure):  # Generic
    pass
class wmEventHandler_Op(Structure):  # Operator
    pass
class wmWindow(Structure):
    pass

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")
            break
        handle = handle.contents.next
    else:
        print("No running modals")

5 Likes

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?

Cheers!

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.

Thanks!