def gui_thread_schedule(
    func: Callable[..., T],
    args: tuple[Any, ...] | None = None,
    timeout: int | None = None,
    kwargs: dict[str, Any] | None = None,
) -> T | None:
    """
    Schedules the given callable to be executed on the GUI thread. If the current thread is the GUI thread, the callable
    is executed immediately.

    :raises: Any exception raised by the callable.
    :returns: The result of the callable, or None if the callable timed out.
    """
    if is_gui_thread():
        return func(*(args or ()), **(kwargs or {}))

    event = ExecuteCodeEvent(func, args=args, kwargs=kwargs)

    try:
        QCoreApplication.postEvent(GlobalInfo.main_window, event)
    except RuntimeError:
        # the application is exiting and the main window has been destroyed. just let it go
        return None

    event.event.wait(timeout=timeout)  # TODO: unsafe. to be fixed later.
    if not event.event.is_set():
        # it timed out without getting scheduled to execute...
        return None

    if event.exception is not None:
        raise event.exception

    return event.result

    Schedules the given callable to be executed on the GUI thread. If the current thread is the GUI thread, the callable
    is executed immediately.

    :raises: Any exception raised by the callable.
    :returns: The result of the callable, or None if the callable timed out.
    
angrmanagement/logic/threads.py