/* SPDX-FileCopyrightText: 2007 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once

/** \file
 * \ingroup wm
 *
 * \page wmpage windowmanager
 * \section wmabout About windowmanager
 * \ref wm handles events received from \ref GHOST and manages
 * the screens, areas and input for Blender
 * \section wmnote NOTE
 * \todo document
 */

#include <optional>
#include <string>

#include "DNA_windowmanager_types.h"

#include "BLI_array.hh"
#include "BLI_bounds_types.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_function_ref.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_sys_types.h"

#include "WM_keymap.hh"
#include "WM_types.hh"

struct ARegion;
struct GPUViewport;
struct ID;
struct IDProperty;
struct ImBuf;
struct ImageFormatData;
struct Main;
struct MenuType;
struct PointerRNA;
struct PropertyRNA;
struct ScrArea;
struct View3D;
struct ViewLayer;
struct bContext;
struct rcti;
struct uiListType;
struct WorkSpace;
struct WorkSpaceLayout;
struct wmDrag;
struct wmDropBox;
struct wmEvent;
struct wmEventHandler_Dropbox;
struct wmEventHandler_Keymap;
struct wmEventHandler_Op;
struct wmEventHandler_UI;
struct wmGenericUserData;
struct wmGesture;
struct wmJob;
struct wmJobWorkerStatus;
struct wmOperator;
struct wmOperatorType;
struct wmPaintCursor;
struct wmTabletData;

#ifdef WITH_INPUT_NDOF
struct wmNDOFMotionData;
#endif

#ifdef WITH_XR_OPENXR
struct wmXrRuntimeData;
struct wmXrSessionState;
#endif

namespace blender::bke::id {
class IDRemapper;
}

namespace blender::asset_system {
class AssetRepresentation;
}

/* General API. */

/**
 * Used for setting app-template from the command line:
 * - non-empty string: overrides.
 * - empty string: override, using no app template.
 * - NULL: clears override.
 */
void WM_init_state_app_template_set(const char *app_template);
const char *WM_init_state_app_template_get();

/**
 * Called when no ghost system was initialized.
 */
void WM_init_state_size_set(int stax, int stay, int sizx, int sizy);
/**
 * For border-less and border windows set from command-line.
 */
void WM_init_state_fullscreen_set();
void WM_init_state_normal_set();
void WM_init_state_maximized_set();
void WM_init_state_start_with_console_set(bool value);
void WM_init_window_focus_set(bool do_it);
void WM_init_native_pixels(bool do_it);
void WM_init_input_devices();

/**
 * Initialize Blender and load the startup file & preferences
 * (only called once).
 */
void WM_init(bContext *C, int argc, const char **argv);
/**
 * Main exit function (implementation).
 *
 * \note Unlike #WM_exit this does not call `exit()`,
 * the caller is responsible for this.
 *
 * \param C: The context or null, a null context implies `do_user_exit_actions == false` &
 * prevents some editor-exit operations from running.
 * \param do_python_exit: Exit the Python interpreter (unless `WITH_PYTHON_MODULE` is enabled).
 * \param do_user_exit_actions: When enabled perform actions associated with a user
 * having been using Blender then exiting. Actions such as writing the auto-save
 * and writing any changes to preferences.
 * Set to false in background mode or when exiting because of failed command line argument parsing.
 * In general automated actions where the user isn't making changes should pass in false too.
 */
void WM_exit_ex(bContext *C, bool do_python_exit, bool do_user_exit_actions);

/**
 * Main exit function to close Blender ordinarily.
 *
 * \note Use #wm_exit_schedule_delayed() to close Blender from an operator.
 * Might leak memory otherwise.
 *
 * \param exit_code: Passed to #exit, typically #EXIT_SUCCESS or #EXIT_FAILURE should be used.
 * With failure being used for an early exit when parsing command line arguments fails.
 * Note that any exit-code besides #EXIT_SUCCESS calls #WM_exit_ex with its `do_user_exit_actions`
 * argument set to false.
 */
void WM_exit(bContext *C, int exit_code) ATTR_NORETURN;

void WM_main(bContext *C) ATTR_NORETURN;

/**
 * Show the splash screen as needed on startup.
 *
 * The splash may not show depending on a file being loaded and user preferences.
 */
void WM_init_splash_on_startup(bContext *C);
/**
 * Show the splash screen.
 */
void WM_init_splash(bContext *C);

void WM_init_gpu();

/**
 * Return an identifier for the underlying GHOST implementation.
 * \warning Use of this function should be limited & never for compatibility checks.
 * see: #GHOST_ISystem::getSystemBackend for details.
 */
const char *WM_ghost_backend();

enum eWM_CapabilitiesFlag {
  /** Ability to warp the cursor (set its location). */
  WM_CAPABILITY_CURSOR_WARP = (1 << 0),
  /**
   * Window position access, support for the following.
   * - Getting window positions.
   * - Setting window positions.
   * - Setting positions for new windows.
   *
   * Currently there is no need to distinguish between these different cases
   * so a single flag is used.
   *
   * When omitted, it isn't possible to know where windows are located in relation to each other.
   * Operations such as applying events from one window to another or detecting the non-active
   * window under the cursor are not supported.
   */
  WM_CAPABILITY_WINDOW_POSITION = (1 << 1),
  /**
   * The windowing system supports a separate primary clipboard
   * (typically set when interactively selecting text).
   */
  WM_CAPABILITY_PRIMARY_CLIPBOARD = (1 << 2),
  /**
   * Reading from the back-buffer is supported.
   */
  WM_CAPABILITY_GPU_FRONT_BUFFER_READ = (1 << 3),
  /** Ability to copy/paste system clipboard images. */
  WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4),
  /** Ability to sample a color outside of Blender windows. */
  WM_CAPABILITY_DESKTOP_SAMPLE = (1 << 5),
  /** Support for IME input methods. */
  WM_CAPABILITY_INPUT_IME = (1 << 6),
  /** Trackpad physical scroll detection. */
  WM_CAPABILITY_TRACKPAD_PHYSICAL_DIRECTION = (1 << 7),
  /** Support for window decoration styles. */
  WM_CAPABILITY_WINDOW_DECORATION_STYLES = (1 << 8),
  /** Support for the "Hyper" modifier key. */
  WM_CAPABILITY_KEYBOARD_HYPER_KEY = (1 << 9),
  /** The initial value, indicates the value needs to be set by inspecting GHOST. */
  WM_CAPABILITY_INITIALIZED = (1u << 31),
};
ENUM_OPERATORS(eWM_CapabilitiesFlag, WM_CAPABILITY_WINDOW_DECORATION_STYLES)

/**
 * Return the capabilities of the windowing system.
 *
 * \note Some callers need to check `G.background == false` before this function.
 * See inline code-comments for details.
 */
eWM_CapabilitiesFlag WM_capabilities_flag();

void WM_check(bContext *C);
void WM_reinit_gizmomap_all(Main *bmain);

/**
 * Needed for cases when operators are re-registered
 * (when operator type pointers are stored).
 */
void WM_script_tag_reload();

wmWindow *WM_window_find_under_cursor(wmWindow *win,
                                      const int event_xy[2],
                                      int r_event_xy_other[2]);

/**
 * Knowing the area, return its screen.
 * \note This should typically be avoided, only use when the context is not available.
 */
wmWindow *WM_window_find_by_area(wmWindowManager *wm, const ScrArea *area);

/**
 * Return the color of the pixel at the current mouse cursor position on the desktop, whether in a
 * Blender window or not. Returns false on failure or if not supported by the platform.
 */
bool WM_desktop_cursor_sample_read(float r_col[3]);

/**
 * Read pixels from the front-buffer (fast).
 *
 * \note Internally this depends on the front-buffer state,
 * for a slower but more reliable method of reading pixels,
 * use #WM_window_pixels_read_from_offscreen.
 * Fast pixel access may be preferred for file-save thumbnails.
 *
 * \warning Drawing (swap-buffers) immediately before calling this function causes
 * the front-buffer state to be invalid under some EGL configurations.
 */
uint8_t *WM_window_pixels_read_from_frontbuffer(const wmWindowManager *wm,
                                                const wmWindow *win,
                                                int r_size[2]);
/** A version of #WM_window_pixels_read_from_frontbuffer that samples a pixel at `pos`. */
void WM_window_pixels_read_sample_from_frontbuffer(const wmWindowManager *wm,
                                                   const wmWindow *win,
                                                   const int pos[2],
                                                   float r_col[3]);

/**
 * Draw the window & read pixels from an off-screen buffer
 * (slower than #WM_window_pixels_read_from_frontbuffer).
 *
 * \note This is needed because the state of the front-buffer may be damaged
 * (see in-line code comments for details).
 */
uint8_t *WM_window_pixels_read_from_offscreen(bContext *C, wmWindow *win, int r_size[2]);
/** A version of #WM_window_pixels_read_from_offscreen that samples a pixel at `pos`. */
bool WM_window_pixels_read_sample_from_offscreen(bContext *C,
                                                 wmWindow *win,
                                                 const int pos[2],
                                                 float r_col[3]);

/**
 * Read from the screen.
 *
 * \note Use off-screen drawing when front-buffer reading is not supported.
 */
uint8_t *WM_window_pixels_read(bContext *C, wmWindow *win, int r_size[2]);
/**
 * Read a single pixel from the screen.
 *
 * \note Use off-screen drawing when front-buffer reading is not supported.
 */
bool WM_window_pixels_read_sample(bContext *C, wmWindow *win, const int pos[2], float r_col[3]);

/**
 * Support for native pixel size
 *
 * \note macOS retina opens window in size X, but it has up to 2 x more pixels.
 */
int WM_window_native_pixel_x(const wmWindow *win);
int WM_window_native_pixel_y(const wmWindow *win);

blender::int2 WM_window_native_pixel_size(const wmWindow *win);

void WM_window_native_pixel_coords(const wmWindow *win, int *x, int *y);
/**
 * Get boundaries usable by all window contents, including global areas.
 */
void WM_window_rect_calc(const wmWindow *win, rcti *r_rect);
/**
 * Get boundaries usable by screen-layouts, excluding global areas.
 * \note Depends on #UI_SCALE_FAC. Should that be outdated, call #WM_window_set_dpi first.
 */
void WM_window_screen_rect_calc(const wmWindow *win, rcti *r_rect);
bool WM_window_is_main_top_level(const wmWindow *win);
bool WM_window_is_fullscreen(const wmWindow *win);
bool WM_window_is_maximized(const wmWindow *win);

/**
 * Some editor data may need to be synced with scene data (3D View camera and layers).
 * This function ensures data is synced for editors
 * in visible work-spaces and their visible layouts.
 */
void WM_windows_scene_data_sync(const ListBase *win_lb, Scene *scene) ATTR_NONNULL();
Scene *WM_windows_scene_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
    ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
ViewLayer *WM_windows_view_layer_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
    ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
WorkSpace *WM_windows_workspace_get_from_screen(const wmWindowManager *wm, const bScreen *screen)
    ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;

Scene *WM_window_get_active_scene(const wmWindow *win) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
/**
 * \warning Only call outside of area/region loops.
 */
void WM_window_set_active_scene(Main *bmain, bContext *C, wmWindow *win, Scene *scene)
    ATTR_NONNULL();
WorkSpace *WM_window_get_active_workspace(const wmWindow *win)
    ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
void WM_window_set_active_workspace(bContext *C, wmWindow *win, WorkSpace *workspace)
    ATTR_NONNULL(1);
WorkSpaceLayout *WM_window_get_active_layout(const wmWindow *win)
    ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
void WM_window_set_active_layout(wmWindow *win, WorkSpace *workspace, WorkSpaceLayout *layout)
    ATTR_NONNULL(1);
/**
 * Get the active screen of the active workspace in \a win.
 */
bScreen *WM_window_get_active_screen(const wmWindow *win) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
void WM_window_set_active_screen(wmWindow *win, WorkSpace *workspace, bScreen *screen)
    ATTR_NONNULL(1);

ViewLayer *WM_window_get_active_view_layer(const wmWindow *win)
    ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;
void WM_window_set_active_view_layer(wmWindow *win, ViewLayer *view_layer) ATTR_NONNULL(1);
void WM_window_ensure_active_view_layer(wmWindow *win) ATTR_NONNULL(1);

bool WM_window_is_temp_screen(const wmWindow *win) ATTR_WARN_UNUSED_RESULT;

void *WM_system_gpu_context_create();
void WM_system_gpu_context_dispose(void *context);
void WM_system_gpu_context_activate(void *context);
void WM_system_gpu_context_release(void *context);

/** #WM_window_open alignment. */
enum eWindowAlignment {
  WIN_ALIGN_ABSOLUTE = 0,
  WIN_ALIGN_LOCATION_CENTER,
  WIN_ALIGN_PARENT_CENTER,
};

/**
 * \param rect: Position & size of the window.
 * \param space_type: #SPACE_VIEW3D, #SPACE_INFO, ... (#eSpace_Type).
 * \param toplevel: Not a child owned by other windows. A peer of main window.
 * \param dialog: whether this should be made as a dialog-style window
 * \param temp: whether this is considered a short-lived window
 * \param alignment: how this window is positioned relative to its parent
 * \param area_setup_fn: An optional callback which can be used to initialize the area
 * before it's initialized. When set, `space_type` should be #SPACE_EMTPY,
 * so the setup function can take a blank area and initialize it.
 * \param area_setup_user_data: User data argument passed to `area_setup_fn`.
 * \return the window or NULL in case of failure.
 */
wmWindow *WM_window_open(bContext *C,
                         const char *title,
                         const rcti *rect_unscaled,
                         int space_type,
                         bool toplevel,
                         bool dialog,
                         bool temp,
                         eWindowAlignment alignment,
                         void (*area_setup_fn)(bScreen *screen, ScrArea *area, void *user_data),
                         void *area_setup_user_data) ATTR_NONNULL(1, 3);

void WM_window_set_dpi(const wmWindow *win);

/**
 * Give a title to a window. With "Title" unspecified or nullptr, it is generated
 * automatically from window settings and areas. Only use custom title when really needed.
 */
void WM_window_title(wmWindowManager *wm, wmWindow *win, const char *title = nullptr);

bool WM_stereo3d_enabled(wmWindow *win, bool skip_stereo3d_check);

/* Window Decoration Styles. */

/**
 * Flags for #WM_window_decoration_set_style().
 *
 * \note To be kept in sync with #GHOST_TWindowDecorationFlags.
 */
enum eWM_WindowDecorationStyleFlag {
  /** No decoration styling. */
  WM_WINDOW_DECORATION_STYLE_NONE = 0,
  /** Colored TitleBar. */
  WM_WINDOW_DECORATION_STYLE_COLORED_TITLEBAR = (1 << 0),
};
ENUM_OPERATORS(eWM_WindowDecorationStyleFlag, WM_WINDOW_DECORATION_STYLE_COLORED_TITLEBAR)

/**
 * Get the window decoration style flags.
 */
eWM_WindowDecorationStyleFlag WM_window_decoration_style_flags_get(const wmWindow *win);
/**
 * Set window decoration style flags.
 * Use before calling #WM_window_decoration_style_apply.
 */
void WM_window_decoration_style_flags_set(const wmWindow *win,
                                          eWM_WindowDecorationStyleFlag style_flags);
/**
 * Apply the window decoration style using the current style flags and by parsing style
 * settings from the current Blender theme.
 * The screen parameter is optional, and can be passed for enhanced theme parsing.
 *
 * \note Avoid calling this function directly, prefer sending an #NC_WINDOW
 * notification when #WM_CAPABILITY_WINDOW_DECORATION_STYLES is supported instead.
 */
void WM_window_decoration_style_apply(const wmWindow *win, const bScreen *screen = nullptr);

/* `wm_files.cc`. */

void WM_file_autoexec_init(const char *filepath);
/**
 * \param use_scripts_autoexec_check: When true, script auto-execution checks excluded directories.
 * Note that this is passed in as an argument because `filepath` may reference a path to recover.
 * In this case the that used for exclusion is the recovery path which is only known once
 * the file has been loaded.
 */
bool WM_file_read(bContext *C,
                  const char *filepath,
                  const bool use_scripts_autoexec_check,
                  ReportList *reports);
void WM_file_autosave_init(wmWindowManager *wm);
bool WM_file_recover_last_session(bContext *C,
                                  const bool use_scripts_autoexec_check,
                                  ReportList *reports);
void WM_file_tag_modified();

/**
 * \note `scene` (and related `view_layer` and `v3d`) pointers may be NULL,
 * in which case no instantiation of linked objects, collections etc. will be performed.
 */
ID *WM_file_link_datablock(Main *bmain,
                           Scene *scene,
                           ViewLayer *view_layer,
                           View3D *v3d,
                           const char *filepath,
                           short id_code,
                           const char *id_name,
                           int flag);
/**
 * \note `scene` (and related `view_layer` and `v3d`) pointers may be NULL,
 * in which case no instantiation of appended objects, collections etc. will be performed.
 */
ID *WM_file_append_datablock(Main *bmain,
                             Scene *scene,
                             ViewLayer *view_layer,
                             View3D *v3d,
                             const char *filepath,
                             short id_code,
                             const char *id_name,
                             int flag);
void WM_lib_reload(Library *lib, bContext *C, ReportList *reports);

/* Mouse cursors. */

void WM_cursor_set(wmWindow *win, int curs);
bool WM_cursor_set_from_tool(wmWindow *win, const ScrArea *area, const ARegion *region);
void WM_cursor_modal_set(wmWindow *win, int val);
void WM_cursor_modal_restore(wmWindow *win);
/**
 * To allow usage all over, we do entire WM.
 */
void WM_cursor_wait(bool val);
/**
 * Enable cursor grabbing, optionally hiding the cursor and wrapping cursor-motion
 * within a sub-region of the window.
 *
 * \param wrap: an enum (#WM_CURSOR_WRAP_NONE, #WM_CURSOR_WRAP_XY... etc).
 * \param wrap_region: Window-relative region for cursor wrapping (when `wrap` is
 * #WM_CURSOR_WRAP_XY). When NULL, the window bounds are used for wrapping.
 *
 * \note The current grab state can be accessed by #wmWindowManager.grabcursor although.
 */
void WM_cursor_grab_enable(wmWindow *win,
                           eWM_CursorWrapAxis wrap,
                           const rcti *wrap_region,
                           bool hide);
void WM_cursor_grab_disable(wmWindow *win, const int mouse_ungrab_xy[2]);
/**
 * After this you can call restore too.
 */
void WM_cursor_time(wmWindow *win, int nr);

wmPaintCursor *WM_paint_cursor_activate(short space_type,
                                        short region_type,
                                        bool (*poll)(bContext *C),
                                        wmPaintCursorDraw draw,
                                        void *customdata);

bool WM_paint_cursor_end(wmPaintCursor *handle);
void WM_paint_cursor_remove_by_type(wmWindowManager *wm, void *draw_fn, void (*free)(void *));
void WM_paint_cursor_tag_redraw(wmWindow *win, ARegion *region);

/**
 * Set the cursor location in window coordinates (compatible with #wmEvent.xy).
 *
 * \note Some platforms don't support this, check: #WM_CAPABILITY_WINDOW_POSITION
 * before relying on this functionality.
 */
void WM_cursor_warp(wmWindow *win, int x, int y);

/**
 * The default size of a cursor without any DPI scaling.
 */
#define WM_CURSOR_DEFAULT_LOGICAL_SIZE 24

/**
 * \return the preferred logical size for the cursor
 * (before DPI/Hi-DPI scaling is applied).
 */
uint WM_cursor_preferred_logical_size();

/* Handlers. */

enum eWM_EventHandlerFlag {
  /** After this handler all others are ignored. */
  WM_HANDLER_BLOCKING = (1 << 0),
  /** Handler accepts double key press events. */
  WM_HANDLER_ACCEPT_DBL_CLICK = (1 << 1),

  /* Internal. */
  /** Handler tagged to be freed in #wm_handlers_do(). */
  WM_HANDLER_DO_FREE = (1 << 7),
};
ENUM_OPERATORS(eWM_EventHandlerFlag, WM_HANDLER_DO_FREE)

using EventHandlerPoll = bool (*)(const wmWindow *win,
                                  const ScrArea *area,
                                  const ARegion *region,
                                  const wmEvent *event);
wmEventHandler_Keymap *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap *keymap);
wmEventHandler_Keymap *WM_event_add_keymap_handler_poll(ListBase *handlers,
                                                        wmKeyMap *keymap,
                                                        EventHandlerPoll poll);

/**
 * \return true when the `event` should be handled by the 2D views masked region.
 *
 * \note uses the #EventHandlerPoll signature.
 */
bool WM_event_handler_region_v2d_mask_poll(const wmWindow *win,
                                           const ScrArea *area,
                                           const ARegion *region,
                                           const wmEvent *event);
/**
 * \return true when the `event` is inside the marker region.
 *
 * \note There are no checks that markers are displayed.
 */
bool WM_event_handler_region_marker_poll(const wmWindow *win,
                                         const ScrArea *area,
                                         const ARegion *region,
                                         const wmEvent *event);

/**
 * A version of #WM_event_handler_region_v2d_mask_poll which excludes events
 * (returning false) in the marker region.
 *
 * \note uses the #EventHandlerPoll signature.
 */
bool WM_event_handler_region_v2d_mask_no_marker_poll(const wmWindow *win,
                                                     const ScrArea *area,
                                                     const ARegion *region,
                                                     const wmEvent *event);

wmEventHandler_Keymap *WM_event_add_keymap_handler_v2d_mask(ListBase *handlers, wmKeyMap *keymap);
/**
 * \note Priorities not implemented yet, for time being just insert in begin of list.
 */
wmEventHandler_Keymap *WM_event_add_keymap_handler_priority(ListBase *handlers,
                                                            wmKeyMap *keymap,
                                                            int priority);

struct wmEventHandler_KeymapResult {
  wmKeyMap *keymaps[3];
  int keymaps_len;
};

using wmEventHandler_KeymapDynamicFn = void (*)(wmWindowManager *wm,
                                                wmWindow *win,
                                                wmEventHandler_Keymap *handler,
                                                wmEventHandler_KeymapResult *km_result);

void WM_event_get_keymap_from_toolsystem_with_gizmos(wmWindowManager *wm,
                                                     wmWindow *win,
                                                     wmEventHandler_Keymap *handler,
                                                     wmEventHandler_KeymapResult *km_result);
void WM_event_get_keymap_from_toolsystem(wmWindowManager *wm,
                                         wmWindow *win,
                                         wmEventHandler_Keymap *handler,
                                         wmEventHandler_KeymapResult *km_result);

wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic(
    ListBase *handlers, wmEventHandler_KeymapDynamicFn keymap_fn, void *user_data);

void WM_event_remove_keymap_handler(ListBase *handlers, wmKeyMap *keymap);

void WM_event_set_keymap_handler_post_callback(wmEventHandler_Keymap *handler,
                                               void(keymap_tag)(wmKeyMap *keymap,
                                                                wmKeyMapItem *kmi,
                                                                void *user_data),
                                               void *user_data);
void WM_event_get_keymaps_from_handler(wmWindowManager *wm,
                                       wmWindow *win,
                                       wmEventHandler_Keymap *handler,
                                       wmEventHandler_KeymapResult *km_result);

wmKeyMapItem *WM_event_match_keymap_item(bContext *C, wmKeyMap *keymap, const wmEvent *event);

wmKeyMapItem *WM_event_match_keymap_item_from_handlers(
    bContext *C, wmWindowManager *wm, wmWindow *win, ListBase *handlers, const wmEvent *event);

bool WM_event_match(const wmEvent *winevent, const wmKeyMapItem *kmi);

using wmUIHandlerFunc = int (*)(bContext *C, const wmEvent *event, void *userdata);
using wmUIHandlerRemoveFunc = void (*)(bContext *C, void *userdata);

wmEventHandler_UI *WM_event_add_ui_handler(const bContext *C,
                                           ListBase *handlers,
                                           wmUIHandlerFunc handle_fn,
                                           wmUIHandlerRemoveFunc remove_fn,
                                           void *user_data,
                                           eWM_EventHandlerFlag flag);

/**
 * Return the first modal operator of type \a ot or NULL.
 */
wmOperator *WM_operator_find_modal_by_type(wmWindow *win, const wmOperatorType *ot);

/**
 * \param postpone: Enable for `win->modalhandlers`,
 * this is in a running for () loop in wm_handlers_do().
 */
void WM_event_remove_ui_handler(ListBase *handlers,
                                wmUIHandlerFunc handle_fn,
                                wmUIHandlerRemoveFunc remove_fn,
                                void *user_data,
                                bool postpone);
void WM_event_remove_area_handler(ListBase *handlers, void *area);
void WM_event_free_ui_handler_all(bContext *C,
                                  ListBase *handlers,
                                  wmUIHandlerFunc handle_fn,
                                  wmUIHandlerRemoveFunc remove_fn);

/**
 * Add a modal handler to `win`, `area` and `region` may optionally be NULL.
 */
wmEventHandler_Op *WM_event_add_modal_handler_ex(wmWindow *win,
                                                 ScrArea *area,
                                                 ARegion *region,
                                                 wmOperator *op) ATTR_NONNULL(1, 4);
wmEventHandler_Op *WM_event_add_modal_handler(bContext *C, wmOperator *op) ATTR_NONNULL(1, 2);
void WM_event_remove_model_handler(ListBase *handlers, const wmOperator *op, bool postpone)
    ATTR_NONNULL(1, 2);

void WM_event_remove_modal_handler_all(const wmOperator *op, bool postpone) ATTR_NONNULL(1);

/**
 * Modal handlers store a pointer to an area which might be freed while the handler runs.
 * Use this function to NULL all handler pointers to \a old_area.
 */
void WM_event_modal_handler_area_replace(wmWindow *win,
                                         const ScrArea *old_area,
                                         ScrArea *new_area);
/**
 * Modal handlers store a pointer to a region which might be freed while the handler runs.
 * Use this function to NULL all handler pointers to \a old_region.
 */
void WM_event_modal_handler_region_replace(wmWindow *win,
                                           const ARegion *old_region,
                                           ARegion *new_region);
void WM_event_ui_handler_region_popup_replace(wmWindow *win,
                                              const ARegion *old_region,
                                              ARegion *new_region);

/**
 * Called on exit or remove area, only here call cancel callback.
 */
void WM_event_remove_handlers(bContext *C, ListBase *handlers);

wmEventHandler_Dropbox *WM_event_add_dropbox_handler(ListBase *handlers, ListBase *dropboxes);

/* Mouse. */
void WM_event_add_mousemove(wmWindow *win);

#ifdef WITH_INPUT_NDOF
/* 3D mouse. */
void WM_ndof_deadzone_set(float deadzone);
#endif
/* Notifiers. */
void WM_event_add_notifier_ex(wmWindowManager *wm,
                              const wmWindow *win,
                              unsigned int type,
                              void *reference);
void WM_event_add_notifier(const bContext *C, unsigned int type, void *reference);
void WM_main_add_notifier(unsigned int type, void *reference);
/**
 * Clear notifiers by reference, Used so listeners don't act on freed data.
 */
void WM_main_remove_notifier_reference(const void *reference);
void WM_main_remap_editor_id_reference(const blender::bke::id::IDRemapper &mappings);

/* Reports. */

/**
 * Show the report in the info header.
 * \param win: When NULL, a best-guess is used.
 */
void WM_report_banner_show(wmWindowManager *wm, wmWindow *win) ATTR_NONNULL(1);
/**
 * Hide all currently displayed banners and abort their timer.
 */
void WM_report_banners_cancel(Main *bmain);
/**
 * Move a whole list of reports to the WM ReportList, and show the banner.
 *
 * \note In case the given \a reports is a `nullptr`, or has its #RPT_OP_HOLD flag set, this
 * function does nothing.
 *
 * \note The list of reports from given \a reports is moved into the list of WM's reports, so the
 * given \a reports will be empty after calling this function. The \a reports #ReportList data
 * itself is not freed or cleared though, and remains fully usable after this call.
 *
 * \params reports The #ReportList from which to move reports to the WM one, may be `nullptr`.
 * \params wm the WindowManager to add given \a reports to. If `nullptr`, the first WM of current
 * #G_MAIN will be used.
 */
void WM_reports_from_reports_move(wmWindowManager *wm, ReportList *reports);

/**
 * Report directly to the window manager without any context.
 *
 * \warning This function should almost always be avoided in favor of #BKE_report,
 * if this isn't possible, a code-comment must be included explaining why.
 *
 * Global reports are bad practice because the caller can't handle or suppress them.
 * This means for example, if an automated tasks/scripts can generate many reports
 * that are shown to the user without any way to control error handling.
 *
 * When used in operators it prevents the Python script from raising an exception
 * form the error as it should do, showing a popup instead.
 */
void WM_global_report(eReportType type, const char *message);
/**
 * Report directly to the window manager without any context.
 *
 * \warning This function should almost always be avoided in favor of #BKE_reportf,
 * if this isn't possible, a code-comment must be included explaining why.
 *
 * See #WM_global_report for details.
 */
void WM_global_reportf(eReportType type, const char *format, ...) ATTR_PRINTF_FORMAT(2, 3);

wmEvent *WM_event_add(wmWindow *win, const wmEvent *event_to_add) ATTR_NONNULL(1, 2);

void wm_event_init_from_window(wmWindow *win, wmEvent *event);

/**
 * At maximum, every time_step seconds it triggers `event_type` events.
 */
wmTimer *WM_event_timer_add(wmWindowManager *wm,
                            wmWindow *win,
                            wmEventType event_type,
                            double time_step);
wmTimer *WM_event_timer_add_notifier(wmWindowManager *wm,
                                     wmWindow *win,
                                     unsigned int type,
                                     double time_step);

void WM_event_timer_free_data(wmTimer *timer);
/**
 * Free all timers immediately.
 *
 * \note This should only be used on-exit,
 * in all other cases timers should be tagged for removal by #WM_event_timer_remove.
 */
void WM_event_timers_free_all(wmWindowManager *wm);

/**
 * Mark the given `timer` to be removed, actual removal and deletion is deferred and handled
 * internally by the window manager code.
 */
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *win, wmTimer *timer);
void WM_event_timer_remove_notifier(wmWindowManager *wm, wmWindow *win, wmTimer *timer);
/**
 * To (de)activate running timers temporary.
 */
void WM_event_timer_sleep(wmWindowManager *wm, wmWindow *win, wmTimer *timer, bool do_sleep);

/* Operator API, default callbacks. */

/**
 * Helper to get select and tweak-transform to work conflict free and as desired. See
 * #WM_operator_properties_generic_select() for details.
 *
 * To be used together with #WM_generic_select_invoke() and
 * #WM_operator_properties_generic_select().
 */
wmOperatorStatus WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event);
/**
 * Helper to get select and tweak-transform to work conflict free and as desired. See
 * #WM_operator_properties_generic_select() for details.
 *
 * To be used together with #WM_generic_select_modal() and
 * #WM_operator_properties_generic_select().
 */
wmOperatorStatus WM_generic_select_invoke(bContext *C, wmOperator *op, const wmEvent *event);
void WM_operator_view3d_unit_defaults(bContext *C, wmOperator *op);
int WM_operator_smooth_viewtx_get(const wmOperator *op);
/**
 * Invoke callback, uses enum property named "type".
 */
wmOperatorStatus WM_menu_invoke_ex(bContext *C, wmOperator *op, wmOperatorCallContext opcontext);
wmOperatorStatus WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *event);
/**
 * Call an existent menu. The menu can be created in C or Python.
 */
void WM_menu_name_call(bContext *C, const char *menu_name, short context);

wmOperatorStatus WM_enum_search_invoke(bContext *C, wmOperator *op, const wmEvent *event);

/**
 * Invoke callback, confirm menu + exec.
 */
wmOperatorStatus WM_operator_confirm(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_operator_confirm_or_exec(bContext *C, wmOperator *op, const wmEvent *event);

/**
 * Like WM_operator_confirm, but with more options and can't be used as an invoke directly.
 */
wmOperatorStatus WM_operator_confirm_ex(bContext *C,
                                        wmOperator *op,
                                        const char *title = nullptr,
                                        const char *message = nullptr,
                                        const char *confirm_text = nullptr,
                                        int icon = 0, /* ALERT_ICON_WARNING. */
                                        bool cancel_default = false);

/**
 * Invoke callback, file selector "filepath" unset + exec.
 *
 * #wmOperatorType.invoke, opens file-select if path property not set, otherwise executes.
 */
wmOperatorStatus WM_operator_filesel(bContext *C, wmOperator *op, const wmEvent *event);
bool WM_operator_filesel_ensure_ext_imtype(wmOperator *op, const ImageFormatData *im_format);
/** Callback for #wmOperatorType.poll. */
bool WM_operator_winactive(bContext *C);
/**
 * Invoke callback, exec + redo popup.
 *
 * Same as #WM_operator_props_popup but don't use operator redo.
 * just wraps #WM_operator_props_dialog_popup.
 */
wmOperatorStatus WM_operator_props_popup_confirm(bContext *C,
                                                 wmOperator *op,
                                                 const wmEvent *event);

wmOperatorStatus WM_operator_props_popup_confirm_ex(
    bContext *C,
    wmOperator *op,
    const wmEvent *event,
    std::optional<std::string> title = std::nullopt,
    std::optional<std::string> confirm_text = std::nullopt,
    bool cancel_default = false);

/**
 * Same as #WM_operator_props_popup but call the operator first,
 * This way - the button values correspond to the result of the operator.
 * Without this, first access to a button will make the result jump, see #32452.
 */
wmOperatorStatus WM_operator_props_popup_call(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_operator_props_popup(bContext *C, wmOperator *op, const wmEvent *event);

wmOperatorStatus WM_operator_props_dialog_popup(
    bContext *C,
    wmOperator *op,
    int width,
    std::optional<std::string> title = std::nullopt,
    std::optional<std::string> confirm_text = std::nullopt,
    bool cancel_default = false);

wmOperatorStatus WM_operator_redo_popup(bContext *C, wmOperator *op);
wmOperatorStatus WM_operator_ui_popup(bContext *C, wmOperator *op, int width);

/**
 * Can't be used as an invoke directly, needs message arg (can be NULL).
 */
wmOperatorStatus WM_operator_confirm_message_ex(bContext *C,
                                                wmOperator *op,
                                                const char *title,
                                                int icon,
                                                const char *message,
                                                wmOperatorCallContext opcontext);
wmOperatorStatus WM_operator_confirm_message(bContext *C, wmOperator *op, const char *message);

/* Operator API. */

void WM_operator_free(wmOperator *op);
void WM_operator_free_all_after(wmWindowManager *wm, wmOperator *op);
/**
 * Use with extreme care!
 * Properties, custom-data etc - must be compatible.
 *
 * \param op: Operator to assign the type to.
 * \param ot: Operator type to assign.
 */
void WM_operator_type_set(wmOperator *op, wmOperatorType *ot);
void WM_operator_stack_clear(wmWindowManager *wm);
/**
 * This function is needed in the case when an addon id disabled
 * while a modal operator it defined is running.
 */
void WM_operator_handlers_clear(wmWindowManager *wm, wmOperatorType *ot);

bool WM_operator_poll(bContext *C, wmOperatorType *ot);
bool WM_operator_poll_context(bContext *C, wmOperatorType *ot, short context);
/**
 * For running operators with frozen context (modal handlers, menus).
 *
 * \param store: Store properties for re-use when an operator has finished
 * (unless #PROP_SKIP_SAVE is set).
 *
 * \warning do not use this within an operator to call itself! #29537.
 */
wmOperatorStatus WM_operator_call_ex(bContext *C, wmOperator *op, bool store);
wmOperatorStatus WM_operator_call(bContext *C, wmOperator *op);
/**
 * This is intended to be used when an invoke operator wants to call exec on itself
 * and is basically like running op->type->exec() directly, no poll checks no freeing,
 * since we assume whoever called invoke will take care of that
 */
wmOperatorStatus WM_operator_call_notest(bContext *C, wmOperator *op);
/**
 * Execute this operator again, put here so it can share above code
 */
wmOperatorStatus WM_operator_repeat(bContext *C, wmOperator *op);
wmOperatorStatus WM_operator_repeat_last(bContext *C, wmOperator *op);
/**
 * \return true if #WM_operator_repeat can run.
 * Simple check for now but may become more involved.
 * To be sure the operator can run call `WM_operator_poll(C, op->type)` also, since this call
 * checks if #WM_operator_repeat() can run at all, not that it WILL run at any time.
 */
bool WM_operator_repeat_check(const bContext *C, wmOperator *op);
bool WM_operator_is_repeat(const bContext *C, const wmOperator *op);

bool WM_operator_name_poll(bContext *C, const char *opstring);
/**
 * Invokes operator in context.
 *
 * \param event: Optionally pass in an event to use when context uses one of the
 * `WM_OP_INVOKE_*` values. When left unset the #wmWindow.eventstate will be used,
 * this can cause problems for operators that read the events type - for example,
 * storing the key that was pressed so as to be able to detect its release.
 * In these cases it's necessary to forward the current event being handled.
 */
wmOperatorStatus WM_operator_name_call_ptr(bContext *C,
                                           wmOperatorType *ot,
                                           wmOperatorCallContext context,
                                           PointerRNA *properties,
                                           const wmEvent *event);
/** See #WM_operator_name_call_ptr. */
wmOperatorStatus WM_operator_name_call(bContext *C,
                                       const char *opstring,
                                       wmOperatorCallContext context,
                                       PointerRNA *properties,
                                       const wmEvent *event);
wmOperatorStatus WM_operator_name_call_with_properties(bContext *C,
                                                       const char *opstring,
                                                       wmOperatorCallContext context,
                                                       IDProperty *properties,
                                                       const wmEvent *event);
/**
 * Similar to #WM_operator_name_call called with #WM_OP_EXEC_DEFAULT context.
 *
 * - #wmOperatorType is used instead of operator name since python already has the operator type.
 * - `poll()` must be called by python before this runs.
 * - reports can be passed to this function (so python can report them as exceptions).
 */
wmOperatorStatus WM_operator_call_py(bContext *C,
                                     wmOperatorType *ot,
                                     wmOperatorCallContext context,
                                     PointerRNA *properties,
                                     ReportList *reports,
                                     bool is_undo);

void WM_operator_name_call_ptr_with_depends_on_cursor(bContext *C,
                                                      wmOperatorType *ot,
                                                      wmOperatorCallContext opcontext,
                                                      PointerRNA *properties,
                                                      const wmEvent *event,
                                                      blender::StringRef drawstr);

/**
 * Similar to the function above except its uses ID properties used for key-maps and macros.
 */
void WM_operator_properties_alloc(PointerRNA **ptr, IDProperty **properties, const char *opstring);

/**
 * Make props context sensitive or not.
 */
void WM_operator_properties_sanitize(PointerRNA *ptr, bool no_context);

/**
 * Set all props to their default.
 *
 * \param do_update: Only update un-initialized props.
 *
 * \note There's nothing specific to operators here.
 * This could be made a general function.
 */
bool WM_operator_properties_default(PointerRNA *ptr, bool do_update);
/**
 * Remove all props without #PROP_SKIP_SAVE or #PROP_SKIP_PRESET.
 */
void WM_operator_properties_reset(wmOperator *op);
void WM_operator_properties_create(PointerRNA *ptr, const char *opstring);
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot);
void WM_operator_properties_clear(PointerRNA *ptr);
void WM_operator_properties_free(PointerRNA *ptr);

bool WM_operator_ui_poll(wmOperatorType *ot, PointerRNA *ptr);
/**
 * Return false, if the UI should be disabled.
 */
bool WM_operator_check_ui_enabled(const bContext *C, const char *idname);

IDProperty *WM_operator_last_properties_ensure_idprops(wmOperatorType *ot);
void WM_operator_last_properties_ensure(wmOperatorType *ot, PointerRNA *ptr);
wmOperator *WM_operator_last_redo(const bContext *C);
/**
 * Use for drag & drop a path or name with operators invoke() function.
 * Returns null if no operator property is set to identify the file or ID to use.
 */
ID *WM_operator_drop_load_path(bContext *C, wmOperator *op, short idcode);

bool WM_operator_last_properties_init(wmOperator *op);
bool WM_operator_last_properties_store(wmOperator *op);

/* `wm_operator_props.cc` */

void WM_operator_properties_confirm_or_exec(wmOperatorType *ot);

/** Flags for #WM_operator_properties_filesel. */
enum eFileSel_Flag {
  WM_FILESEL_RELPATH = 1 << 0,
  WM_FILESEL_DIRECTORY = 1 << 1,
  WM_FILESEL_FILENAME = 1 << 2,
  WM_FILESEL_FILEPATH = 1 << 3,
  WM_FILESEL_FILES = 1 << 4,
  /** Show the properties sidebar by default. */
  WM_FILESEL_SHOW_PROPS = 1 << 5,
};
ENUM_OPERATORS(eFileSel_Flag, WM_FILESEL_SHOW_PROPS)

/** Action for #WM_operator_properties_filesel. */
enum eFileSel_Action {
  FILE_OPENFILE = 0,
  FILE_SAVE = 1,
};

/**
 * Default properties for file-select.
 */
void WM_operator_properties_filesel(wmOperatorType *ot,
                                    int filter,
                                    short type,
                                    eFileSel_Action action,
                                    eFileSel_Flag flag,
                                    short display,
                                    short sort);

/**
 * Tries to pass \a id to an operator via either a "session_uid" or a "name" property defined in
 * the properties of \a ptr. The former is preferred, since it works properly with linking and
 * library overrides (which may both result in multiple IDs with the same name and type).
 *
 * Also see #WM_operator_properties_id_lookup() and
 * #WM_operator_properties_id_lookup_from_name_or_session_uid()
 */
void WM_operator_properties_id_lookup_set_from_id(PointerRNA *ptr, const ID *id);
/**
 * Tries to find an ID in \a bmain. There needs to be either a "session_uid" int or "name" string
 * property defined and set. The former has priority. See #WM_operator_properties_id_lookup() for a
 * helper to add the properties.
 */
ID *WM_operator_properties_id_lookup_from_name_or_session_uid(Main *bmain,
                                                              PointerRNA *ptr,
                                                              enum ID_Type type);
/**
 * Check if either the "session_uid" or "name" property is set inside \a ptr. If this is the case
 * the ID can be looked up by #WM_operator_properties_id_lookup_from_name_or_session_uid().
 */
bool WM_operator_properties_id_lookup_is_set(PointerRNA *ptr);
/**
 * Adds "name" and "session_uid" properties so the caller can tell the operator which ID to act
 * on. See #WM_operator_properties_id_lookup_from_name_or_session_uid(). Both properties will be
 * hidden in the UI and not be saved over consecutive operator calls.
 *
 * \note New operators should probably use "session_uid" only (set \a add_name_prop to #false),
 * since this works properly with linked data and/or library overrides (in both cases, multiple IDs
 * with the same name and type may be present). The "name" property is only kept to not break
 * compatibility with old scripts using some previously existing operators.
 */
void WM_operator_properties_id_lookup(wmOperatorType *ot, const bool add_name_prop);

/**
 * Disable using cursor position,
 * use when view operators are initialized from buttons.
 */
void WM_operator_properties_use_cursor_init(wmOperatorType *ot);
void WM_operator_properties_border(wmOperatorType *ot);
void WM_operator_properties_border_to_rcti(wmOperator *op, rcti *r_rect);
void WM_operator_properties_border_to_rctf(wmOperator *op, rctf *r_rect);
blender::Bounds<blender::int2> WM_operator_properties_border_to_bounds(wmOperator *op);
/**
 * Use with #WM_gesture_box_invoke
 */
void WM_operator_properties_gesture_box_ex(wmOperatorType *ot, bool deselect, bool extend);
void WM_operator_properties_gesture_box(wmOperatorType *ot);
void WM_operator_properties_gesture_box_select(wmOperatorType *ot);
void WM_operator_properties_gesture_box_zoom(wmOperatorType *ot);
/**
 * Use with #WM_gesture_lasso_invoke
 */
void WM_operator_properties_gesture_lasso(wmOperatorType *ot);
/**
 * Use with #WM_gesture_polyline_invoke
 */
void WM_operator_properties_gesture_polyline(wmOperatorType *ot);
/**
 * Use with #WM_gesture_straightline_invoke
 */
void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor);
/**
 * Use with #WM_gesture_circle_invoke
 */
void WM_operator_properties_gesture_circle(wmOperatorType *ot);
/**
 * See #ED_select_pick_params_from_operator to initialize parameters defined here.
 */
void WM_operator_properties_mouse_select(wmOperatorType *ot);
void WM_operator_properties_select_all(wmOperatorType *ot);
void WM_operator_properties_select_action(wmOperatorType *ot, int default_action, bool hide_gui);
/**
 * Only for select/de-select.
 */
void WM_operator_properties_select_action_simple(wmOperatorType *ot,
                                                 int default_action,
                                                 bool hide_gui);
/**
 * Use for all select random operators.
 * Adds properties: percent, seed, action.
 */
void WM_operator_properties_select_random(wmOperatorType *ot);
int WM_operator_properties_select_random_seed_increment_get(wmOperator *op);
void WM_operator_properties_select_operation(wmOperatorType *ot);
/**
 * \note Some tools don't support XOR/AND.
 */
void WM_operator_properties_select_operation_simple(wmOperatorType *ot);
void WM_operator_properties_select_walk_direction(wmOperatorType *ot);
/**
 * Selecting and tweaking items are overlapping operations. Getting both to work without conflicts
 * requires special care. See
 * https://wiki.blender.org/wiki/Human_Interface_Guidelines/Selection#Select-tweaking for the
 * desired behavior.
 *
 * For default click selection (with no modifier keys held), the select operators can do the
 * following:
 * - On a mouse press on an unselected item, change selection and finish immediately after.
 *   This sends an undo push and allows transform to take over should a click-drag event be caught.
 * - On a mouse press on a selected item, don't change selection state, but start modal execution
 *   of the operator. Idea is that we wait with deselecting other items until we know that the
 *   intention wasn't to tweak (mouse press+drag) all selected items.
 * - If a click-drag is recognized before the release event happens, cancel the operator,
 *   so that transform can take over and no undo-push is sent.
 * - If the release event occurs rather than a click-drag one,
 *   deselect all items but the one under the cursor, and finish the modal operator.
 *
 * This utility, together with #WM_generic_select_invoke() and #WM_generic_select_modal() should
 * help getting the wanted behavior to work. Most generic logic should be handled in these, so that
 * the select operators only have to care for the case dependent handling.
 *
 * Every select operator has slightly different requirements, e.g. sequencer strip selection
 * also needs to account for handle selection. This should be the baseline behavior though.
 */
void WM_operator_properties_generic_select(wmOperatorType *ot);

struct CheckerIntervalParams {
  /** Bypass when set to zero. */
  int nth;
  int skip;
  int offset;
};
/**
 * \param nth_can_disable: Enable if we want to be able to select no interval at all.
 */
void WM_operator_properties_checker_interval(wmOperatorType *ot, bool nth_can_disable);
void WM_operator_properties_checker_interval_from_op(wmOperator *op,
                                                     CheckerIntervalParams *op_params);
bool WM_operator_properties_checker_interval_test(const CheckerIntervalParams *op_params,
                                                  int depth);

/**
 * Operator as a Python command.
 *
 * Return a string representation of the operator,
 * with the arguments that it runs so Python can run it again.
 *
 * When calling from an existing #wmOperator, better to use simple version:
 * `WM_operator_pystring(C, op);`
 *
 * \note Both \a op and \a opptr may be `NULL` (\a op is only used for macro operators).
 */
std::string WM_operator_pystring_ex(bContext *C,
                                    wmOperator *op,
                                    bool all_args,
                                    bool macro_args,
                                    wmOperatorType *ot,
                                    PointerRNA *opptr);
std::string WM_operator_pystring(bContext *C, wmOperator *op, bool all_args, bool macro_args);

std::string WM_operator_pystring_abbreviate(std::string str, int str_len_max);

std::optional<std::string> WM_prop_pystring_assign(bContext *C,
                                                   PointerRNA *ptr,
                                                   PropertyRNA *prop,
                                                   int index);
/**
 * Convert: `some.op` -> `SOME_OT_op` or leave as-is.
 * \return the length of `dst`.
 */
size_t WM_operator_bl_idname(char *dst, const char *src) ATTR_NONNULL(1, 2);
/**
 * Convert: `SOME_OT_op` -> `some.op` or leave as-is.
 * \return the length of `dst`.
 */
size_t WM_operator_py_idname(char *dst, const char *src) ATTR_NONNULL(1, 2);
/**
 * Sanity check to ensure #WM_operator_bl_idname won't fail.
 * \returns true when there are no problems with \a idname, otherwise report an error.
 */
bool WM_operator_py_idname_ok_or_report(ReportList *reports,
                                        const char *classname,
                                        const char *idname);
/**
 * Return true when an operators name follows the `SOME_OT_op` naming convention.
 */
bool WM_operator_bl_idname_is_valid(const char *idname);

/**
 * Calculate the path to `ptr` from context `C`, or return NULL if it can't be calculated.
 */
std::optional<std::string> WM_context_path_resolve_property_full(const bContext *C,
                                                                 const PointerRNA *ptr,
                                                                 PropertyRNA *prop,
                                                                 int index);
std::optional<std::string> WM_context_path_resolve_full(bContext *C, const PointerRNA *ptr);

/* `wm_operator_type.cc` */

wmOperatorType *WM_operatortype_find(const char *idname, bool quiet);
blender::Span<wmOperatorType *> WM_operatortypes_registered_get();
void WM_operatortype_append(void (*opfunc)(wmOperatorType *ot));
void WM_operatortype_append_ptr(void (*opfunc)(wmOperatorType *ot, void *userdata),
                                void *userdata);
void WM_operatortype_append_macro_ptr(void (*opfunc)(wmOperatorType *ot, void *userdata),
                                      void *userdata);
/**
 * Called on initialize WM_exit().
 */
void WM_operatortype_remove_ptr(wmOperatorType *ot);
bool WM_operatortype_remove(const char *idname);
/**
 * Remove memory of all previously executed tools.
 */
void WM_operatortype_last_properties_clear_all();

void WM_operatortype_idname_visit_for_search(
    const bContext *C,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const char *edit_text,
    blender::FunctionRef<void(StringPropertySearchVisitParams)> visit_fn);

/**
 * Tag all operator-properties of \a ot defined after calling this, until
 * the next #WM_operatortype_props_advanced_end call (if available), with
 * #OP_PROP_TAG_ADVANCED. Previously defined ones properties not touched.
 *
 * Calling this multiple times without a call to #WM_operatortype_props_advanced_end,
 * all calls after the first one are ignored. Meaning all proprieties defined after the
 * first call are tagged as advanced.
 *
 * This doesn't do the actual tagging, #WM_operatortype_props_advanced_end does which is
 * called for all operators during registration (see #wm_operatortype_append__end).
 */
void WM_operatortype_props_advanced_begin(wmOperatorType *ot);
/**
 * Tags all operator-properties of \a ot defined since the first
 * #WM_operatortype_props_advanced_begin call,
 * or the last #WM_operatortype_props_advanced_end call, with #OP_PROP_TAG_ADVANCED.
 *
 * \note This is called for all operators during registration (see #wm_operatortype_append__end).
 * So it does not need to be explicitly called in operator-type definition.
 */
void WM_operatortype_props_advanced_end(wmOperatorType *ot);

#define WM_operatortype_prop_tag(property, tags) \
  { \
    CHECK_TYPE(tags, eOperatorPropTags); \
    RNA_def_property_tags(prop, tags); \
  } \
  (void)0

/**
 * \note Names have to be static for now.
 */
wmOperatorType *WM_operatortype_append_macro(const char *idname,
                                             const char *name,
                                             const char *description,
                                             int flag);
wmOperatorTypeMacro *WM_operatortype_macro_define(wmOperatorType *ot, const char *idname);

std::string WM_operatortype_name(wmOperatorType *ot, PointerRNA *properties);
std::string WM_operatortype_description(bContext *C, wmOperatorType *ot, PointerRNA *properties);
/**
 * Use when we want a label, preferring the description.
 */
std::string WM_operatortype_description_or_name(bContext *C,
                                                wmOperatorType *ot,
                                                PointerRNA *properties);

/** Check the #OPTYPE_DEPENDS_ON_CURSOR flag and the callback. */
bool WM_operator_depends_on_cursor(bContext &C, wmOperatorType &ot, PointerRNA *properties);

/* `wm_operator_utils.cc` */

/**
 * Allow an operator with only and execute function to run modally,
 * re-doing the action, using vertex coordinate store/restore instead of operator undo.
 */
void WM_operator_type_modal_from_exec_for_object_edit_coords(wmOperatorType *ot);

/* `wm_uilist_type.cc` */

/**
 * Called on initialize #WM_init()
 */
void WM_uilisttype_init();
uiListType *WM_uilisttype_find(blender::StringRef idname, bool quiet);
bool WM_uilisttype_add(uiListType *ult);
void WM_uilisttype_remove_ptr(Main *bmain, uiListType *ult);
void WM_uilisttype_free();

/**
 * The "full" list-ID is an internal name used for storing and identifying a list. It is built like
 * this:
 * `{uiListType.idname}_{list_id}`, whereby `list_id` is an optional parameter passed to
 * `UILayout.template_list()`. If it is not set, the full list-ID is just `{uiListType.idname}_`.
 *
 * Note that whenever the Python API refers to the list-ID, it's the short, "non-full" one it
 * passed to `UILayout.template_list()`. C code can query that through
 * #WM_uilisttype_list_id_get().
 */
void WM_uilisttype_to_full_list_id(const uiListType *ult,
                                   const char *list_id,
                                   char r_full_list_id[]);
/**
 * Get the "non-full" list-ID, see #WM_uilisttype_to_full_list_id() for details.
 *
 * \note Assumes `uiList.list_id` was set using #WM_uilisttype_to_full_list_id()!
 */
const char *WM_uilisttype_list_id_get(const uiListType *ult, uiList *list);

/* `wm_menu_type.cc` */

/**
 * \note Called on initialize #WM_init().
 */
void WM_menutype_init();
MenuType *WM_menutype_find(blender::StringRef idname, bool quiet);
blender::Span<MenuType *> WM_menutypes_registered_get();
bool WM_menutype_add(MenuType *mt);
void WM_menutype_freelink(MenuType *mt);
void WM_menutype_free();
bool WM_menutype_poll(bContext *C, MenuType *mt);

void WM_menutype_idname_visit_for_search(
    const bContext *C,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const char *edit_text,
    blender::FunctionRef<void(StringPropertySearchVisitParams)> visit_fn);

/* `wm_panel_type.cc` */

/**
 * Called on initialize #WM_init().
 */
void WM_paneltype_init();
void WM_paneltype_clear();
PanelType *WM_paneltype_find(blender::StringRef idname, bool quiet);
bool WM_paneltype_add(PanelType *pt);
void WM_paneltype_remove(PanelType *pt);

void WM_paneltype_idname_visit_for_search(
    const bContext *C,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const char *edit_text,
    blender::FunctionRef<void(StringPropertySearchVisitParams)> visit_fn);

/* `wm_gesture_ops.cc` */

wmOperatorStatus WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_box_cancel(bContext *C, wmOperator *op);
wmOperatorStatus WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_circle_cancel(bContext *C, wmOperator *op);
wmOperatorStatus WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_lines_cancel(bContext *C, wmOperator *op);
wmOperatorStatus WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_lasso_cancel(bContext *C, wmOperator *op);
wmOperatorStatus WM_gesture_polyline_invoke(bContext *C, wmOperator *op, const wmEvent *event);
wmOperatorStatus WM_gesture_polyline_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_polyline_cancel(bContext *C, wmOperator *op);
/**
 * helper function, we may want to add options for conversion to view space
 */
blender::Array<blender::int2> WM_gesture_lasso_path_to_array(bContext *C, wmOperator *op);

wmOperatorStatus WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event);
/**
 * This invoke callback starts the straight-line gesture with a viewport preview to the right side
 * of the line.
 */
wmOperatorStatus WM_gesture_straightline_active_side_invoke(bContext *C,
                                                            wmOperator *op,
                                                            const wmEvent *event);
/**
 * This modal callback calls exec once per mouse move event while the gesture is active with the
 * updated line start and end values, so it can be used for tools that have a real time preview
 * (like a gradient updating in real time over the mesh).
 */
wmOperatorStatus WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event);
/**
 * This modal one-shot callback only calls exec once after the gesture finishes without any updates
 * during the gesture execution. Should be used for operations that are intended to be applied once
 * without real time preview (like a trimming tool that only applies the bisect operation once
 * after finishing the gesture as the bisect operation is too heavy to be computed in real time for
 * a preview).
 */
wmOperatorStatus WM_gesture_straightline_oneshot_modal(bContext *C,
                                                       wmOperator *op,
                                                       const wmEvent *event);
void WM_gesture_straightline_cancel(bContext *C, wmOperator *op);

/* Gesture manager API. */

/**
 * Context checked on having screen, window and area.
 */
wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent *event, int type);
void WM_gesture_end(wmWindow *win, wmGesture *gesture);
void WM_gestures_remove(wmWindow *win);
void WM_gestures_free_all(wmWindow *win);
bool WM_gesture_is_modal_first(const wmGesture *gesture);

/* File-selecting support. */

/**
 * The idea here is to keep a handler alive on window queue, owning the operator.
 * The file window can send event to make it execute, thus ensuring
 * executing happens outside of lower level queues, with UI refreshed.
 * Should also allow multi-window solutions.
 */
void WM_event_add_fileselect(bContext *C, wmOperator *op);
void WM_event_fileselect_event(wmWindowManager *wm, void *ophandle, int eventval);

/* Event consecutive data. */

/** Return a borrowed reference to the custom-data. */
void *WM_event_consecutive_data_get(wmWindow *win, const char *id);
/** Set the custom-data (and own the pointer), free with #MEM_freeN. */
void WM_event_consecutive_data_set(wmWindow *win, const char *id, void *custom_data);
/** Clear and free the consecutive custom-data. */
void WM_event_consecutive_data_free(wmWindow *win);

/**
 * Sets the active region for this space from the context.
 *
 * \see #BKE_area_find_region_active_win
 */
void WM_operator_region_active_win_set(bContext *C);

/**
 * Indented for use in a selection (picking) operators #wmOperatorType::invoke callback
 * to implement click-drag, where the initial click selects and the drag action
 * grabs or performs box-select (for example).
 *
 * - In this case, returning `OPERATOR_FINISHED` causes the PRESS event
 *   to be handled and prevents further CLICK (on release) or DRAG (on cursor motion)
 *   from being generated & handled.
 *
 * - Returning `OPERATOR_FINISHED | OPERATOR_PASS_THROUGH` allows for CLICK/DRAG but only makes
 *   sense if the event's value is PRESS. If the operator was already mapped to a CLICK/DRAG event,
 *   a single CLICK/DRAG could invoke multiple operators.
 *
 * This function handles the details of checking the operator return value,
 * clearing #OPERATOR_PASS_THROUGH when the #wmEvent::val is not #KM_PRESS.
 *
 * \note Combining selection with other actions should only be used
 * in situations where selecting doesn't change the visibility of other items.
 * Since it means for example click-drag to box select could hide-show elements the user
 * intended to box-select. In this case it's preferred to select on CLICK instead of PRESS
 * (see the Outliner use of click-drag).
 */
wmOperatorStatus WM_operator_flag_only_pass_through_on_press(wmOperatorStatus retval,
                                                             const wmEvent *event);

/* Drag and drop. */

/**
 * Start dragging immediately with the given data.
 * Note that \a poin should be valid allocated and not on stack.
 */
void WM_event_start_drag(
    bContext *C, int icon, eWM_DragDataType type, void *poin, unsigned int flags);
/**
 * Create and fill the dragging data, but don't start dragging just yet (unlike
 * #WM_event_start_drag()). Must be followed up by #WM_event_start_prepared_drag(), otherwise the
 * returned pointer will leak memory.
 *
 * Note that \a poin should be valid allocated and not on stack.
 */
wmDrag *WM_drag_data_create(
    bContext *C, int icon, eWM_DragDataType type, void *poin, unsigned int flags);
/**
 * Invoke dragging using the given \a drag data.
 */
void WM_event_start_prepared_drag(bContext *C, wmDrag *drag);
void WM_event_drag_image(wmDrag *drag, const ImBuf *imb, float scale);
/**
 * Overrides the `drag.poin` event to include all selected files in the space file where the event
 * started.
 */
void WM_event_drag_path_override_poin_data_with_space_file_paths(const bContext *, wmDrag *drag);
void WM_event_drag_preview_icon(wmDrag *drag, int icon_id);
void WM_drag_free(wmDrag *drag);
void WM_drag_data_free(eWM_DragDataType dragtype, void *poin);
void WM_drag_free_list(ListBase *lb);
wmDropBox *WM_dropbox_add(ListBase *lb,
                          const char *idname,
                          bool (*poll)(bContext *C, wmDrag *drag, const wmEvent *event),
                          void (*copy)(bContext *C, wmDrag *drag, wmDropBox *drop),
                          void (*cancel)(Main *bmain, wmDrag *drag, wmDropBox *drop),
                          WMDropboxTooltipFunc tooltip);
/**
 * Ensure operator pointers & properties are valid after operators have been added/removed.
 */
void WM_dropbox_update_ot();

void WM_drag_draw_item_name_fn(bContext *C, wmWindow *win, wmDrag *drag, const int xy[2]);
void WM_drag_draw_default_fn(bContext *C, wmWindow *win, wmDrag *drag, const int xy[2]);
/**
 * `spaceid` / `regionid` are zero for window drop maps.
 */
ListBase *WM_dropboxmap_find(const char *idname, int spaceid, int regionid);

/* ID drag and drop. */

/**
 * \param flag_extra: Additional linking flags (from #eFileSel_Params_Flag).
 */
ID *WM_drag_asset_id_import(const bContext *C, wmDragAsset *asset_drag, int flag_extra);
bool WM_drag_asset_will_import_linked(const wmDrag *drag);
void WM_drag_add_local_ID(wmDrag *drag, ID *id, ID *from_parent);
ID *WM_drag_get_local_ID(const wmDrag *drag, short idcode);
ID *WM_drag_get_local_ID_from_event(const wmEvent *event, short idcode);
/**
 * Check if the drag data is either a local ID or an external ID asset of type \a idcode.
 */
bool WM_drag_is_ID_type(const wmDrag *drag, int idcode);

/**
 * \note Does not store \a asset in any way, so it's fine to pass a temporary.
 */
wmDragAsset *WM_drag_create_asset_data(const blender::asset_system::AssetRepresentation *asset,
                                       const AssetImportSettings &import_settings);

wmDragAsset *WM_drag_get_asset_data(const wmDrag *drag, int idcode);
AssetMetaData *WM_drag_get_asset_meta_data(const wmDrag *drag, int idcode);
/**
 * When dragging a local ID, return that. Otherwise, if dragging an asset-handle, link or append
 * that depending on what was chosen by the drag-box (currently append only in fact).
 *
 * Use #WM_drag_free_imported_drag_ID() as cancel callback of the drop-box, so that the asset
 * import is rolled back if the drop operator fails.
 */
ID *WM_drag_get_local_ID_or_import_from_asset(const bContext *C, const wmDrag *drag, int idcode);

/**
 * \brief Free asset ID imported for canceled drop.
 *
 * If the asset was imported (linked/appended) using #WM_drag_get_local_ID_or_import_from_asset()`
 * (typically via a #wmDropBox.copy() callback), we want the ID to be removed again if the drop
 * operator cancels.
 * This is for use as #wmDropBox.cancel() callback.
 */
void WM_drag_free_imported_drag_ID(Main *bmain, wmDrag *drag, wmDropBox *drop);

wmDragAssetCatalog *WM_drag_get_asset_catalog_data(const wmDrag *drag);

/**
 * \note Does not store \a asset in any way, so it's fine to pass a temporary.
 */
void WM_drag_add_asset_list_item(wmDrag *drag,
                                 const blender::asset_system::AssetRepresentation *asset);

const ListBase *WM_drag_asset_list_get(const wmDrag *drag);

const char *WM_drag_get_item_name(wmDrag *drag);

/* Paths drag and drop. */
/**
 * \param paths: The paths to drag. Values will be copied into the drag data so the passed strings
 * may be destructed.
 */
wmDragPath *WM_drag_create_path_data(blender::Span<const char *> paths);
/**
 *  If `drag` contains path data, returns the first path int he path list.
 */
const char *WM_drag_get_single_path(const wmDrag *drag);
/**
 * If `drag` contains path data, returns the first path in the path list that matches a
 * a `file_type`.
 *
 * \param drag: The drag that could contain drag path data.
 * \param file_type: #eFileSel_File_Types bit flag.
 */
const char *WM_drag_get_single_path(const wmDrag *drag, int file_type);
blender::Span<std::string> WM_drag_get_paths(const wmDrag *drag);
/**
 * If `drag` contains path data, returns if any file path match a `file_type`.
 *
 * \param drag: The drag that could contain drag path data.
 * \param file_type: #eFileSel_File_Types bit flag.
 */
bool WM_drag_has_path_file_type(const wmDrag *drag, int file_type);
/**
 * Note that even though the enum return type uses bit-flags, this should never have multiple
 * type-bits set, so `ELEM()` like comparison is possible. To check all paths or to do a bit-flag
 * check use `WM_drag_has_path_file_type(drag, file_type)` instead.
 */
int /*eFileSel_File_Types*/ WM_drag_get_path_file_type(const wmDrag *drag);

const std::string &WM_drag_get_string(const wmDrag *drag);
std::string WM_drag_get_string_firstline(const wmDrag *drag);

/* Set OpenGL viewport and scissor. */
void wmViewport(const rcti *winrct);
void wmPartialViewport(rcti *drawrct, const rcti *winrct, const rcti *partialrct);
void wmWindowViewport(const wmWindow *win);
void wmWindowViewport_ex(const wmWindow *win, float offset);

/* OpenGL utilities with safety check. */
void wmOrtho2(float x1, float x2, float y1, float y2);
/* Use for conventions (avoid hard-coded offsets all over). */

/**
 * Default pixel alignment for regions.
 */
void wmOrtho2_region_pixelspace(const ARegion *region);
void wmOrtho2_pixelspace(float x, float y);
void wmGetProjectionMatrix(float mat[4][4], const rcti *winrct);

/* Threaded Jobs Manager. */
enum eWM_JobFlag {
  WM_JOB_PRIORITY = (1 << 0),
  /**
   * Only one render job can run at a time, this tags them a such. New jobs with this flag will
   * wait on previous ones to finish then.
   */
  WM_JOB_EXCL_RENDER = (1 << 1),
  WM_JOB_PROGRESS = (1 << 2),
};
ENUM_OPERATORS(eWM_JobFlag, WM_JOB_PROGRESS);

/**
 * Identifying jobs by owner alone is unreliable, this isn't saved, order can change.
 */
enum eWM_JobType {
  /** Not a real type, use for querying any type. */
  WM_JOB_TYPE_ANY = 0,

  WM_JOB_TYPE_COMPOSITE,
  WM_JOB_TYPE_RENDER,
  WM_JOB_TYPE_RENDER_PREVIEW, /* UI preview. */
  /** Job for the UI to load previews from the file system (uses OS thumbnail cache). */
  WM_JOB_TYPE_LOAD_PREVIEW, /* UI preview. */
  WM_JOB_TYPE_OBJECT_SIM_OCEAN,
  WM_JOB_TYPE_OBJECT_SIM_FLUID,
  WM_JOB_TYPE_OBJECT_BAKE_TEXTURE,
  WM_JOB_TYPE_OBJECT_BAKE,
  WM_JOB_TYPE_FILESEL_READDIR,
  WM_JOB_TYPE_ASSET_LIBRARY_LOAD,
  WM_JOB_TYPE_CLIP_BUILD_PROXY,
  WM_JOB_TYPE_CLIP_TRACK_MARKERS,
  WM_JOB_TYPE_CLIP_SOLVE_CAMERA,
  WM_JOB_TYPE_CLIP_PREFETCH,
  WM_JOB_TYPE_SEQ_BUILD_PROXY,
  WM_JOB_TYPE_SEQ_BUILD_PREVIEW,
  WM_JOB_TYPE_POINTCACHE,
  WM_JOB_TYPE_DPAINT_BAKE,
  WM_JOB_TYPE_ALEMBIC_IMPORT,
  WM_JOB_TYPE_ALEMBIC_EXPORT,
  WM_JOB_TYPE_USD_IMPORT,
  WM_JOB_TYPE_USD_EXPORT,
  WM_JOB_TYPE_SHADER_COMPILATION,
  WM_JOB_TYPE_STUDIOLIGHT,
  WM_JOB_TYPE_LIGHT_BAKE,
  WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE,
  WM_JOB_TYPE_QUADRIFLOW_REMESH,
  WM_JOB_TYPE_TRACE_IMAGE,
  WM_JOB_TYPE_LINEART,
  WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL,
  WM_JOB_TYPE_SEQ_DRAG_DROP_PREVIEW,
  WM_JOB_TYPE_CALCULATE_SIMULATION_NODES,
  WM_JOB_TYPE_BAKE_GEOMETRY_NODES,
  WM_JOB_TYPE_UV_PACK,
  /* Add as needed, bake, seq proxy build
   * if having hard coded values is a problem. */
};

/**
 * \return current job or adds new job, but doesn't run it.
 *
 * \note every owner only gets a single running job of the same \a job_type (or with the
 * #WM_JOB_EXCL_RENDER flag). Adding a new one will wait for the running job to finish.
 */
wmJob *WM_jobs_get(wmWindowManager *wm,
                   wmWindow *win,
                   const void *owner,
                   const char *name,
                   eWM_JobFlag flag,
                   eWM_JobType job_type);

/**
 * Returns true if job runs, for UI (progress) indicators.
 */
bool WM_jobs_test(const wmWindowManager *wm, const void *owner, int job_type);
float WM_jobs_progress(const wmWindowManager *wm, const void *owner);
const char *WM_jobs_name(const wmWindowManager *wm, const void *owner);
/**
 * Time that job started.
 */
double WM_jobs_starttime(const wmWindowManager *wm, const void *owner);
void *WM_jobs_customdata_from_type(wmWindowManager *wm, const void *owner, int job_type);

bool WM_jobs_is_running(const wmJob *wm_job);
bool WM_jobs_is_stopped(const wmWindowManager *wm, const void *owner);
void *WM_jobs_customdata_get(wmJob *wm_job);
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void *));
void WM_jobs_timer(wmJob *wm_job, double time_step, unsigned int note, unsigned int endnote);
void WM_jobs_delay_start(wmJob *wm_job, double delay_time);

using wm_jobs_start_callback = void (*)(void *custom_data, wmJobWorkerStatus *worker_status);
void WM_jobs_callbacks(wmJob *wm_job,
                       wm_jobs_start_callback startjob,
                       void (*initjob)(void *),
                       void (*update)(void *),
                       void (*endjob)(void *));

void WM_jobs_callbacks_ex(wmJob *wm_job,
                          wm_jobs_start_callback startjob,
                          void (*initjob)(void *),
                          void (*update)(void *),
                          void (*endjob)(void *),
                          void (*completed)(void *),
                          void (*canceled)(void *));

/**
 * Register the given \a wm_job and try to start it immediately.
 *
 * The new \a wm_job will not start immediately and wait for other blocking jobs
 * to end in some way if:
 * - the new job is flagged with #WM_JOB_EXCL_RENDER and another job with the same flag is already
 *   running (blocks it), or...
 * - the new job is __not__ flagged with #WM_JOB_EXCL_RENDER and a job of the same #eWM_JobType is
 *   already running (blocks it).
 *
 * If the new \a wm_job is flagged with #WM_JOB_PRIORITY, it will request other blocking jobs to
 * stop (using #WM_jobs_stop(), so this doesn't take immediate effect) rather than finish its work.
 */
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job);
/**
 * Signal all jobs of this type and owner (if non-null) to stop, timer is required to get
 * handled.
 *
 * Don't pass #WM_JOB_TYPE_ANY as \a job_type. Use #WM_jobs_stop_all_from_owner() instead.
 */
void WM_jobs_stop_type(wmWindowManager *wm, const void *owner, eWM_JobType job_type);
/**
 * Signal all jobs from this owner to stop, timer is required to get handled.
 *
 * Beware of the impact of calling this. For example passing the scene will stop **all** jobs
 * having the scene as owner, even otherwise unrelated jobs.
 */
void WM_jobs_stop_all_from_owner(wmWindowManager *wm, const void *owner) ATTR_NONNULL();
/**
 * Wait until every job ended.
 */
void WM_jobs_kill_all(wmWindowManager *wm);
/**
 * Wait until every job ended, except for one owner (used in undo to keep screen job alive).
 */
void WM_jobs_kill_all_except(wmWindowManager *wm, const void *owner);
/**
 * Terminate thread and timer of all jobs of this type and owner (if non-null).
 *
 * Don't pass #WM_JOB_TYPE_ANY as \a job_type. Use #WM_jobs_kill_all_from_owner() instead.
 */
void WM_jobs_kill_type(wmWindowManager *wm, const void *owner, int job_type);
/**
 * Terminate thread and timer of all jobs from this owner.
 *
 * Beware of the impact of calling this. For example passing the scene will kill **all** jobs
 * having the scene as owner, even otherwise unrelated jobs.
 */
void WM_jobs_kill_all_from_owner(wmWindowManager *wm, const void *owner) ATTR_NONNULL();

bool WM_jobs_has_running(const wmWindowManager *wm);
bool WM_jobs_has_running_type(const wmWindowManager *wm, int job_type);

void WM_job_main_thread_lock_acquire(wmJob *wm_job);
void WM_job_main_thread_lock_release(wmJob *wm_job);

/* Clipboard. */

/**
 * Return text from the clipboard.
 * \param selection: Use the "primary" clipboard, see: #WM_CAPABILITY_PRIMARY_CLIPBOARD.
 * \param ensure_utf8: Ensure the resulting string does not contain invalid UTF8 encoding.
 */
char *WM_clipboard_text_get(bool selection, bool ensure_utf8, int *r_len);
/**
 * Convenience function for pasting to areas of Blender which don't support newlines.
 */
char *WM_clipboard_text_get_firstline(bool selection, bool ensure_utf8, int *r_len);

void WM_clipboard_text_set(const char *buf, bool selection);

/**
 * Returns true if the clipboard contains an image.
 */
bool WM_clipboard_image_available();

/**
 * Get image data from the clipboard.
 * \return The image or null when not found. Caller must free.
 */
ImBuf *WM_clipboard_image_get();

/**
 * Put image data to the clipboard.
 *
 * \param ibuf: the image to set the clipboard to.
 */
bool WM_clipboard_image_set_byte_buffer(ImBuf *ibuf) ATTR_NONNULL(1);

/* Progress. */

void WM_progress_set(wmWindow *win, float progress);
void WM_progress_clear(wmWindow *win);

/* Draw (for screenshot). */

void *WM_draw_cb_activate(wmWindow *win,
                          void (*draw)(const wmWindow *win, void *customdata),
                          void *customdata);
void WM_draw_cb_exit(wmWindow *win, void *handle);
/**
 * High level function to redraw windows.
 *
 * \warning this should be avoided by operators and low-level IO functionality
 * because drawing relies on the event system & depsgraph preparing data for display.
 * An explicit call to draw is error prone since it may attempt to show stale data.
 *
 * With some rare exceptions which require a redraw (e.g. screen-shot & sample screen color)
 * explicitly redrawing should be avoided, see: #92704, #93950, #97627 & #98462.
 */
void WM_redraw_windows(bContext *C);

void WM_draw_region_viewport_ensure(Scene *scene, ARegion *region, short space_type);
void WM_draw_region_viewport_bind(ARegion *region);
void WM_draw_region_viewport_unbind(ARegion *region);

/* Region drawing. */

void WM_draw_region_free(ARegion *region);
GPUViewport *WM_draw_region_get_viewport(ARegion *region);
GPUViewport *WM_draw_region_get_bound_viewport(ARegion *region);

void WM_main_playanim(int argc, const char **argv);

/**
 * Debugging only, convenience function to write on crash.
 * Convenient to save a blend file from a debugger.
 */
bool write_crash_blend();

bool WM_autosave_is_scheduled(wmWindowManager *wm);
/** Flushes all changes from edit modes and stores the auto-save file. */
void WM_autosave_write(wmWindowManager *wm, Main *bmain);

/**
 * Lock the interface for any communication.
 * For #WM_set_locked_interface_with_flags, #lock_flags is #ARegionDrawLockFlags
 */
void WM_set_locked_interface(wmWindowManager *wm, bool lock);
void WM_set_locked_interface_with_flags(wmWindowManager *wm, short lock_flags);

void WM_event_tablet_data_default_set(wmTabletData *tablet_data);

/**
 * For testing only, see #G_FLAG_EVENT_SIMULATE.
 */
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add);

const char *WM_window_cursor_keymap_status_get(const wmWindow *win,
                                               int button_index,
                                               int type_index);
void WM_window_cursor_keymap_status_refresh(bContext *C, wmWindow *win);

void WM_window_status_area_tag_redraw(wmWindow *win);
/**
 * Similar to #BKE_screen_area_map_find_area_xy and related functions,
 * use here since the area is stored in the window manager.
 */
ScrArea *WM_window_status_area_find(wmWindow *win, bScreen *screen);
bool WM_window_modal_keymap_status_draw(bContext *C, wmWindow *win, uiLayout *layout);

/* `wm_event_query.cc` */

/**
 * For debugging only, getting inspecting events manually is tedious.
 */
void WM_event_print(const wmEvent *event);

/**
 * For modal callbacks, check configuration for how to interpret exit when dragging.
 */
bool WM_event_is_modal_drag_exit(const wmEvent *event,
                                 short init_event_type,
                                 short init_event_val);
bool WM_event_is_mouse_drag(const wmEvent *event);
bool WM_event_is_mouse_drag_or_press(const wmEvent *event);
int WM_event_drag_direction(const wmEvent *event);
char WM_event_utf8_to_ascii(const wmEvent *event) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;

/**
 * Detect motion between selection (callers should only use this for selection picking),
 * typically mouse press/click events.
 *
 * \param mval: Region relative coordinates, call with (-1, -1) resets the last cursor location.
 * \returns True when there was motion since last called.
 *
 * NOTE(@ideasman42): The logic used here isn't foolproof.
 * It's possible that users move the cursor past #WM_EVENT_CURSOR_MOTION_THRESHOLD then back to
 * a position within the threshold (between mouse clicks).
 * In practice users never reported this since the threshold is very small (a few pixels).
 * To prevent the unlikely case of values matching from another region,
 * changing regions resets this value to (-1, -1).
 */
bool WM_cursor_test_motion_and_update(const int mval[2]) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT;

/**
 * Return true if this event type is a candidate for being flagged as consecutive.
 *
 * See: #WM_EVENT_IS_CONSECUTIVE doc-string.
 */
bool WM_event_consecutive_gesture_test(const wmEvent *event);
/**
 * Return true if this event should break the chain of consecutive gestures.
 * Practically all intentional user input should, key presses or button clicks.
 */
bool WM_event_consecutive_gesture_test_break(const wmWindow *win, const wmEvent *event);

int WM_event_drag_threshold(const wmEvent *event);
bool WM_event_drag_test(const wmEvent *event, const int prev_xy[2]);
bool WM_event_drag_test_with_delta(const wmEvent *event, const int drag_delta[2]);
void WM_event_drag_start_mval(const wmEvent *event, const ARegion *region, int r_mval[2]);
void WM_event_drag_start_mval_fl(const wmEvent *event, const ARegion *region, float r_mval[2]);
void WM_event_drag_start_xy(const wmEvent *event, int r_xy[2]);

/**
 * Event map that takes preferences into account.
 */
int WM_userdef_event_map(int kmitype);
/**
 * Use so we can check if 'wmEvent.type' is released in modal operators.
 *
 * An alternative would be to add a 'wmEvent.type_nokeymap'... or similar.
 */
int WM_userdef_event_type_from_keymap_type(int kmitype);

#ifdef WITH_INPUT_NDOF
blender::float3 WM_event_ndof_translation_get_for_navigation(const wmNDOFMotionData &ndof);
blender::float3 WM_event_ndof_rotation_get_for_navigation(const wmNDOFMotionData &ndof);
float WM_event_ndof_rotation_get_axis_angle_for_navigation(const wmNDOFMotionData &ndof,
                                                           float axis[3]);

blender::float3 WM_event_ndof_translation_get(const wmNDOFMotionData &ndof);
blender::float3 WM_event_ndof_rotation_get(const wmNDOFMotionData &ndof);
float WM_event_ndof_rotation_get_axis_angle(const wmNDOFMotionData &ndof, float axis[3]);

bool WM_event_ndof_translation_has_pan(const wmNDOFMotionData &ndof);
bool WM_event_ndof_translation_has_zoom(const wmNDOFMotionData &ndof);

#endif /* WITH_INPUT_NDOF */

#ifdef WITH_XR_OPENXR
bool WM_event_is_xr(const wmEvent *event);
#endif

/**
 * If this is a tablet event, return tablet pressure and set `*pen_flip`
 * to 1 if the eraser tool is being used, 0 otherwise.
 */
float WM_event_tablet_data(const wmEvent *event, bool *r_pen_flip, float r_tilt[2]);
bool WM_event_is_tablet(const wmEvent *event);

int WM_event_absolute_delta_x(const wmEvent *event);
int WM_event_absolute_delta_y(const wmEvent *event);

#ifdef WITH_INPUT_IME
bool WM_event_is_ime_switch(const wmEvent *event);
#endif

/* `wm_tooltip.cc` */

using wmTooltipInitFn = ARegion *(*)(bContext *C,
                                     ARegion *region,
                                     int *pass,
                                     double *r_pass_delay,
                                     bool *r_exit_on_event);

void WM_tooltip_immediate_init(
    bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init);
void WM_tooltip_timer_init_ex(bContext *C,
                              wmWindow *win,
                              ScrArea *area,
                              ARegion *region,
                              wmTooltipInitFn init,
                              double delay);
void WM_tooltip_timer_init(
    bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init);
void WM_tooltip_timer_clear(bContext *C, wmWindow *win);
void WM_tooltip_clear(bContext *C, wmWindow *win);
void WM_tooltip_init(bContext *C, wmWindow *win);
void WM_tooltip_refresh(bContext *C, wmWindow *win);
double WM_tooltip_time_closed();

/* `wm_utils.cc` */

wmGenericCallback *WM_generic_callback_steal(wmGenericCallback *callback);
void WM_generic_callback_free(wmGenericCallback *callback);

void WM_generic_user_data_free(wmGenericUserData *wm_userdata);

bool WM_region_use_viewport(ScrArea *area, ARegion *region);

/* `wm_platform.cc` */

/**
 * \return Success.
 */
bool WM_platform_associate_set(bool do_register, bool all_users, char **r_error_msg);

#ifdef WITH_XR_OPENXR
/* `wm_xr_session.cc` */

/**
 * Check if the XR-Session was triggered.
 * If an error happened while trying to start a session, this returns false too.
 */
bool WM_xr_session_exists(const wmXrData *xr);
/**
 * Check if the session is running, according to the OpenXR definition.
 */
bool WM_xr_session_is_ready(const wmXrData *xr);
wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr);
ScrArea *WM_xr_session_area_get(const wmXrData *xr);
void WM_xr_session_base_pose_reset(wmXrData *xr);
bool WM_xr_session_state_viewer_pose_location_get(const wmXrData *xr, float r_location[3]);
bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_rotation[4]);
bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
                                                     float r_viewmat[4][4],
                                                     float *r_focal_len);
bool WM_xr_session_state_controller_grip_location_get(const wmXrData *xr,
                                                      unsigned int subaction_idx,
                                                      float r_location[3]);
bool WM_xr_session_state_controller_grip_rotation_get(const wmXrData *xr,
                                                      unsigned int subaction_idx,
                                                      float r_rotation[4]);
bool WM_xr_session_state_controller_aim_location_get(const wmXrData *xr,
                                                     unsigned int subaction_idx,
                                                     float r_location[3]);
bool WM_xr_session_state_controller_aim_rotation_get(const wmXrData *xr,
                                                     unsigned int subaction_idx,
                                                     float r_rotation[4]);
bool WM_xr_session_state_nav_location_get(const wmXrData *xr, float r_location[3]);
void WM_xr_session_state_nav_location_set(wmXrData *xr, const float location[3]);
bool WM_xr_session_state_nav_rotation_get(const wmXrData *xr, float r_rotation[4]);
void WM_xr_session_state_nav_rotation_set(wmXrData *xr, const float rotation[4]);
bool WM_xr_session_state_nav_scale_get(const wmXrData *xr, float *r_scale);
void WM_xr_session_state_nav_scale_set(wmXrData *xr, float scale);
void WM_xr_session_state_navigation_reset(wmXrSessionState *state);

ARegionType *WM_xr_surface_controller_region_type_get();

/* `wm_xr_actions.cc`. */

/* XR action functions to be called pre-XR session start.
 * NOTE: The "destroy" functions can also be called post-session start. */

bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name);
void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name);
bool WM_xr_action_create(wmXrData *xr,
                         const char *action_set_name,
                         const char *action_name,
                         eXrActionType type,
                         const ListBase *user_paths,
                         wmOperatorType *ot,
                         IDProperty *op_properties,
                         const char *haptic_name,
                         const int64_t *haptic_duration,
                         const float *haptic_frequency,
                         const float *haptic_amplitude,
                         eXrOpFlag op_flag,
                         eXrActionFlag action_flag,
                         eXrHapticFlag haptic_flag);
void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name);
bool WM_xr_action_binding_create(wmXrData *xr,
                                 const char *action_set_name,
                                 const char *action_name,
                                 const char *profile_path,
                                 const ListBase *user_paths,
                                 const ListBase *component_paths,
                                 const float *float_thresholds,
                                 const eXrAxisFlag *axis_flags,
                                 const wmXrPose *poses);
void WM_xr_action_binding_destroy(wmXrData *xr,
                                  const char *action_set_name,
                                  const char *action_name,
                                  const char *profile_path);

/**
 * If action_set_name is NULL, then all action sets will be treated as active.
 */
bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name, bool delayed);

bool WM_xr_controller_pose_actions_set(wmXrData *xr,
                                       const char *action_set_name,
                                       const char *grip_action_name,
                                       const char *aim_action_name);

/**
 * XR action functions to be called post-XR session start.
 */
bool WM_xr_action_state_get(const wmXrData *xr,
                            const char *action_set_name,
                            const char *action_name,
                            const char *subaction_path,
                            wmXrActionState *r_state);
bool WM_xr_haptic_action_apply(wmXrData *xr,
                               const char *action_set_name,
                               const char *action_name,
                               const char *subaction_path,
                               const int64_t *duration,
                               const float *frequency,
                               const float *amplitude);
void WM_xr_haptic_action_stop(wmXrData *xr,
                              const char *action_set_name,
                              const char *action_name,
                              const char *subaction_path);

/* `wm_xr_actionmap.cc` */

XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime,
                                 const char *name,
                                 bool replace_existing);
/**
 * Ensure unique name among all action maps.
 */
void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *actionmap);
XrActionMap *WM_xr_actionmap_add_copy(wmXrRuntimeData *runtime, XrActionMap *am_src);
bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap);
XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name);
void WM_xr_actionmap_clear(XrActionMap *actionmap);
void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime);
ListBase *WM_xr_actionmaps_get(wmXrRuntimeData *runtime);
short WM_xr_actionmap_active_index_get(const wmXrRuntimeData *runtime);
void WM_xr_actionmap_active_index_set(wmXrRuntimeData *runtime, short idx);
short WM_xr_actionmap_selected_index_get(const wmXrRuntimeData *runtime);
void WM_xr_actionmap_selected_index_set(wmXrRuntimeData *runtime, short idx);

XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap,
                                          const char *name,
                                          bool replace_existing);
/**
 * Ensure unique name among all action map items.
 */
void WM_xr_actionmap_item_ensure_unique(XrActionMap *actionmap, XrActionMapItem *ami);
XrActionMapItem *WM_xr_actionmap_item_add_copy(XrActionMap *actionmap, XrActionMapItem *ami_src);
bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami);
XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *name);
/**
 * Similar to #wm_xr_actionmap_item_properties_set()
 * but checks for the #eXrActionType and #wmOperatorType having changed.
 */
void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami);

XrActionMapBinding *WM_xr_actionmap_binding_new(XrActionMapItem *ami,
                                                const char *name,
                                                bool replace_existing);
/**
 * Ensure unique name among all action map bindings.
 */
void WM_xr_actionmap_binding_ensure_unique(XrActionMapItem *ami, XrActionMapBinding *amb);
XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami,
                                                     XrActionMapBinding *amb_src);
bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb);
XrActionMapBinding *WM_xr_actionmap_binding_find(XrActionMapItem *ami, const char *name);
#endif /* WITH_XR_OPENXR */
