Merge pull request #121722 from Repiteo/4.7.2-cherry-picks-1

[4.7] Cherry-picks for the 4.7 branch (future 4.7.2) - 1st batch
This commit is contained in:
Thaddeus Crews authored and GitHub committed 2026-07-27 16:06:32 -05:00
commit 8df813a09b
47 files changed
+755 -227

No files matched your search

@@ -12,7 +12,7 @@ runs:
using: composite
steps:
- name: Restore default cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
id: cache-ping
with:
path: ${{ inputs.scons-cache }}
@@ -27,7 +27,7 @@ runs:
# This is done after pulling the default cache so that PRs can integrate any potential changes
# from the default branch without conflicting with whatever local changes were already made.
- name: Restore local cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
if: github.ref_name != github.event.repository.default_branch
with:
path: ${{ inputs.scons-cache }}
+1 -1
View File
@@ -16,7 +16,7 @@ runs:
run: misc/scripts/purge_cache.py ${{ env.CACHE_TIMESTAMP }} "${{ inputs.scons-cache }}"
- name: Save SCons cache directory
uses: actions/cache/save@v5
uses: actions/cache/save@v6
with:
path: ${{ inputs.scons-cache }}
key: ${{ inputs.cache-name }}|${{ github.ref_name }}|${{ github.sha }}
+1 -1
View File
@@ -22,7 +22,7 @@ runs:
using: composite
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
repository: godotengine/godot-cpp
+1 -1
View File
@@ -16,7 +16,7 @@ runs:
using: composite
steps:
- name: Set up Python 3.x
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
# Semantic version range syntax or exact version of a Python version.
python-version: ${{ inputs.python-version }}
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -119,7 +119,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
sources-changed: ${{ steps.changed-files.outputs.sources_any_changed }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0 # Treeless clone. Slightly less performant than a shallow clone, but makes finding diffs instantaneous.
filter: tree:0 # See: https://github.blog/open-source/git/get-up-to-speed-with-partial-clone-and-shallow-clone/
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -57,7 +57,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1
View File
@@ -49,6 +49,7 @@ int64_t RandomPCG::rand_weighted(const Vector<float> &p_weights) {
const float *weights = p_weights.ptr();
float weights_sum = 0.0;
for (int64_t i = 0; i < weights_size; ++i) {
ERR_FAIL_COND_V_MSG(weights[i] < 0.0, -1, vformat("Weight array contains the negative value %f at index %d.", weights[i], i));
weights_sum += weights[i];
}
+4 -3
View File
@@ -72,7 +72,8 @@ void StringName::cleanup() {
data.sort_custom<DebugSortReferences>();
int unreferenced_stringnames = 0;
int rarely_referenced_stringnames = 0;
for (int i = 0; i < data.size(); i++) {
const int data_size = data.size();
for (int i = 0; i < data_size; i++) {
print_line(itos(i + 1) + ": " + data[i]->name + " - " + itos(data[i]->debug_references));
if (data[i]->debug_references == 0) {
unreferenced_stringnames += 1;
@@ -81,8 +82,8 @@ void StringName::cleanup() {
}
}
print_line(vformat("\nOut of %d StringNames, %d StringNames were never referenced during this run (0 times) (%.2f%%).", data.size(), unreferenced_stringnames, unreferenced_stringnames / float(data.size()) * 100));
print_line(vformat("Out of %d StringNames, %d StringNames were rarely referenced during this run (1-4 times) (%.2f%%).", data.size(), rarely_referenced_stringnames, rarely_referenced_stringnames / float(data.size()) * 100));
print_line(vformat("\nOut of %d StringNames, %d StringNames were never referenced during this run (0 times) (%.2f%%).", data_size, unreferenced_stringnames, unreferenced_stringnames / float(data_size) * 100));
print_line(vformat("Out of %d StringNames, %d StringNames were rarely referenced during this run (1-4 times) (%.2f%%).", data_size, rarely_referenced_stringnames, rarely_referenced_stringnames / float(data_size) * 100));
}
#endif
int lost_strings = 0;
+2 -2
View File
@@ -21,9 +21,9 @@
<return type="int" />
<param index="0" name="weights" type="PackedFloat32Array" />
<description>
Returns a random integer between [code]0[/code] and the size of the array that is passed as a parameter. Each value in the array should be a floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of [code]0[/code] means it will never be returned as an index.
Returns a random integer between [code]0[/code] and the size of the array that is passed as a parameter. Each value in the array should be a non-negative floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of [code]0[/code] means it will never be returned as an index.
For example, if [code skip-lint][0.5, 1, 1, 2][/code] is passed as a parameter, then the method is twice as likely to return [code]3[/code] (the index of the value [code]2[/code]) and twice as unlikely to return [code]0[/code] (the index of the value [code]0.5[/code]) compared to the indices [code]1[/code] and [code]2[/code].
Prints an error and returns [code]-1[/code] if the array is empty.
Prints an error and returns [code]-1[/code] if the array is empty or contains any negative values.
[codeblocks]
[gdscript]
var rng = RandomNumberGenerator.new()
+3 -1
View File
@@ -491,7 +491,9 @@ void EditorVisualProfiler::_graph_tex_draw() {
if (seeking) {
int max_frames = frame_metrics.size();
int frame = cursor_metric_edit->get_value() - (frame_metrics[last_metric].frame_number - max_frames + 1);
int64_t first_visible_frame = static_cast<int64_t>(frame_metrics[last_metric].frame_number) - max_frames + 1;
int frame = (cursor_metric_edit->get_value() - first_visible_frame);
if (frame < 0) {
frame = 0;
}
+4 -3
View File
@@ -1058,10 +1058,11 @@ Ref<Texture2D> CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo
Ref<Texture2D> tex;
switch (p_option.kind) {
case ScriptLanguage::CODE_COMPLETION_KIND_CLASS: {
if (has_theme_icon(p_option.display, EditorStringName(EditorIcons))) {
tex = get_editor_theme_icon(p_option.display);
const String formatted_class_name = p_option.display.unquote();
if (has_theme_icon(formatted_class_name, EditorStringName(EditorIcons))) {
tex = get_editor_theme_icon(formatted_class_name);
} else {
tex = EditorNode::get_singleton()->get_class_icon(p_option.display);
tex = EditorNode::get_singleton()->get_class_icon(formatted_class_name);
if (tex.is_null()) {
tex = get_editor_theme_icon(SNAME("Object"));
}
+10 -7
View File
@@ -224,8 +224,9 @@ void GenericTilePolygonEditor::_base_control_draw() {
base_control->draw_polygon(polygon, v_color);
color.a = 0.7;
for (int j = 0; j < polygon.size(); j++) {
base_control->draw_line(polygon[j], polygon[(j + 1) % polygon.size()], color);
const int polygon_size = polygon.size();
for (int j = 0; j < polygon_size; j++) {
base_control->draw_line(polygon[j], polygon[(j + 1) % polygon_size], color);
}
}
@@ -437,9 +438,10 @@ void GenericTilePolygonEditor::_grab_polygon_segment_point(Vector2 p_pos, const
float closest_distance = grab_threshold * 2.0;
for (unsigned int i = 0; i < polygons.size(); i++) {
const Vector<Vector2> &polygon = polygons[i];
for (int j = 0; j < polygon.size(); j++) {
const int polygon_size = polygon.size();
for (int j = 0; j < polygon_size; j++) {
const Vector2 segment_a = polygon[j];
const Vector2 segment_b = polygon[(j + 1) % polygon.size()];
const Vector2 segment_b = polygon[(j + 1) % polygon_size];
Vector2 closest_point = Geometry2D::get_closest_point_to_segment(point, segment_a, segment_b);
float distance = closest_point.distance_to(point);
if (distance < grab_threshold / editor_zoom_widget->get_zoom() && distance < closest_distance) {
@@ -463,7 +465,8 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur
// Snap to polygon vertices.
bool snapped = false;
for (int i = 0; i < polygon.size(); i++) {
const int polygon_size = polygon.size();
for (int i = 0; i < polygon_size; i++) {
float distance = r_point.distance_to(polygon[i]);
if (distance < p_snap_dist && distance < r_current_snapped_dist) {
snapped_point = polygon[i];
@@ -474,9 +477,9 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur
// Snap to edges if we did not snap to vertices.
if (!snapped) {
for (int i = 0; i < polygon.size(); i++) {
for (int i = 0; i < polygon_size; i++) {
const Vector2 segment_a = polygon[i];
const Vector2 segment_b = polygon[(i + 1) % polygon.size()];
const Vector2 segment_b = polygon[(i + 1) % polygon_size];
Point2 point = Geometry2D::get_closest_point_to_segment(r_point, segment_a, segment_b);
float distance = r_point.distance_to(point);
if (distance < p_snap_dist && distance < r_current_snapped_dist) {
@@ -607,6 +607,7 @@ void TileSetAtlasSourceEditor::_update_atlas_source_inspector() {
void TileSetAtlasSourceEditor::_update_tile_inspector() {
// Update visibility.
tile_inspector->edit(nullptr);
if (tools_button_group->get_pressed_button() == tool_select_button) {
if (!selection.is_empty()) {
tile_proxy_object.instantiate(this);
@@ -1808,6 +1808,7 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
if (p_frames.is_null()) {
frames.unref();
_remove_sprite_node();
close();
return;
}
+2 -2
View File
@@ -3211,6 +3211,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
HashSet<String> loaded_scripts;
List<String> extensions = _get_recognized_extensions();
_set_script_zoom_factor(p_layout->get_value("ScriptEditor", "zoom_factor", 1.0f));
for (const Variant &v : scripts) {
String path = v;
@@ -3306,8 +3308,6 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
}
}
_set_script_zoom_factor(p_layout->get_value("ScriptEditor", "zoom_factor", 1.0f));
restoring_layout = false;
_update_script_names();
+1 -1
View File
@@ -502,7 +502,7 @@ void EditorAutoloadSettings::update_autoload() {
if (old_info.path == info.path) {
// Still the same resource, check status
info.node = old_info.node;
if (info.node && info.node->is_instance()) {
if (info.node && info.node->is_inside_tree()) {
Ref<Script> scr = info.node->get_script();
info.in_editor = scr.is_valid() && scr->is_tool();
if (info.is_singleton == old_info.is_singleton && info.in_editor == old_info.in_editor) {
+1 -1
View File
@@ -20,7 +20,7 @@ else:
deps_folder = os.path.join("bin", "build_deps")
# AccessKit
ac_version = "0.21.2"
ac_version = "0.22.3"
# Create dependencies folder
if not os.path.exists(deps_folder):
+1
View File
@@ -5093,6 +5093,7 @@ NodePath GLTFDocument::_find_material_node_path(Ref<GLTFState> p_state, const Re
}
Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer) {
ERR_FAIL_COND_V_MSG(p_state.is_null(), Ref<GLTFObjectModelProperty>(), "Cannot import object model property because GLTFState is null.");
if (p_state->object_model_properties.has(p_json_pointer)) {
return p_state->object_model_properties[p_json_pointer];
}
+1
View File
@@ -480,6 +480,7 @@ GLTFBufferViewIndex GLTFState::append_data_to_buffers(const Vector<uint8_t> &p_d
}
GLTFNodeIndex GLTFState::append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index) {
ERR_FAIL_COND_V(p_gltf_node.is_null(), GLTFNodeIndex(0));
p_gltf_node->set_parent(p_parent_node_index);
const GLTFNodeIndex new_index = nodes.size();
nodes.append(p_gltf_node);
@@ -282,7 +282,7 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co
for (KeyValue<int, PeerInfo> &E : peers_info) {
E.value.sync_nodes.erase(sid);
E.value.last_watch_usecs.erase(sid);
if (sync->get_net_id()) {
if (sync->get_net_id() && uint32_t(E.key) == tobj.remote_peer) {
E.value.recv_sync_ids.erase(sync->get_net_id());
}
}
@@ -108,6 +108,7 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
// Pause the renderer
godotRenderer.onActivityPaused();
});
pauseGLThread();
}
@Override
@@ -93,6 +93,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
// Pause the renderer
mRenderer.onVkPause();
});
pauseRenderThread();
}
@Override
@@ -93,6 +93,7 @@ internal class VkRenderer {
* Called when the rendering thread is resumed.
*/
fun onVkResume() {
Log.v(TAG, "Renderer resumed")
GodotLib.onRendererResumed()
}
@@ -100,6 +101,7 @@ internal class VkRenderer {
* Called when the rendering thread is paused.
*/
fun onVkPause() {
Log.v(TAG, "Renderer paused")
GodotLib.onRendererPaused()
}
@@ -107,7 +109,7 @@ internal class VkRenderer {
* Invoked when the render thread is in the process of shutting down.
*/
fun onRenderThreadExiting() {
Log.d(TAG, "Destroying Godot Engine")
Log.v(TAG, "Destroying Godot Engine")
GodotLib.ondestroy()
}
}
@@ -32,6 +32,7 @@
package org.godotengine.godot.vulkan
import android.content.Context
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
@@ -51,6 +52,8 @@ import android.view.SurfaceView
*/
open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
companion object {
private val TAG = VkSurfaceView::class.java.simpleName
fun checkState(expression: Boolean, errorMessage: Any) {
check(expression) { errorMessage.toString() }
}
@@ -129,14 +132,17 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.v(TAG, "On surface changed [$format]: $width x $height")
vkThread.onSurfaceChanged(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.v(TAG, "On surface destroyed")
vkThread.onSurfaceDestroyed()
}
override fun surfaceCreated(holder: SurfaceHolder) {
Log.v(TAG, "On surface created")
vkThread.onSurfaceCreated()
}
}
+24 -9
View File
@@ -246,8 +246,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
}
DisplayServerAndroid::get_singleton()->reset_window();
DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton();
ERR_FAIL_NULL(dsa);
dsa->reset_window();
dsa->notify_surface_changed(p_width, p_height);
}
}
}
@@ -288,8 +291,14 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
if (step.get() == STEP_SETUP) {
// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
Main::setup2(false); // The logo is shown in the next frame otherwise we run into rendering issues
// but for Godot purposes, the main thread is the one running the game loop.
// The logo is shown in the next frame otherwise we run into rendering issues.
if (Main::setup2(false) != OK) {
ERR_PRINT("Unable to complete engine setup!");
OS::get_singleton()->alert("Unable to complete engine setup!");
_terminate(env, false);
return false;
}
input_handler = new AndroidInputHandler();
step.increment();
return true;
@@ -316,7 +325,10 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
if (step.get() == STEP_STARTED) {
if (Main::start() != EXIT_SUCCESS) {
return true; // should exit instead and print the error
ERR_PRINT("Unable to start engine!");
OS::get_singleton()->alert("Unable to start engine!");
_terminate(env, false);
return false;
}
godot_java->on_godot_setup_completed(env);
@@ -325,10 +337,13 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
step.increment();
}
DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer);
DisplayServerAndroid::get_singleton()->process_gravity(gravity);
DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer);
DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope);
DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton();
if (dsa) {
dsa->process_accelerometer(accelerometer);
dsa->process_gravity(gravity);
dsa->process_magnetometer(magnetometer);
dsa->process_gyroscope(gyroscope);
}
bool should_swap_buffers = false;
if (os_android->main_loop_iterate(&should_swap_buffers)) {
+18 -7
View File
@@ -383,14 +383,17 @@ bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) {
if (!main_loop) {
return false;
}
DisplayServerAndroid::get_singleton()->reset_swap_buffers_flag();
DisplayServerAndroid::get_singleton()->process_events();
DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton();
ERR_FAIL_NULL_V(dsa, true);
dsa->reset_swap_buffers_flag();
dsa->process_events();
uint64_t current_frames_drawn = Engine::get_singleton()->get_frames_drawn();
bool exit = Main::iteration();
if (r_should_swap_buffers) {
*r_should_swap_buffers = !is_in_low_processor_usage_mode() ||
DisplayServerAndroid::get_singleton()->should_swap_buffers() ||
dsa->should_swap_buffers() ||
RenderingServer::get_singleton()->has_changed() ||
current_frames_drawn != Engine::get_singleton()->get_frames_drawn();
}
@@ -436,17 +439,25 @@ void OS_Android::_on_distraction_free_mode_changed(bool p_enable) {
#endif
void OS_Android::main_loop_focusout() {
DisplayServerAndroid::get_singleton()->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_OUT);
DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton();
if (dsa) {
dsa->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_OUT);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
// Only pause when we are not in PiP mode.
audio_driver_android.set_pause(!DisplayServerAndroid::get_singleton()->is_in_pip_mode());
if (dsa) {
// Only pause when we are not in PiP mode.
audio_driver_android.set_pause(!dsa->is_in_pip_mode());
}
}
void OS_Android::main_loop_focusin() {
DisplayServerAndroid::get_singleton()->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_IN);
DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton();
if (dsa) {
dsa->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_IN);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
}
@@ -32,6 +32,8 @@
#ifdef VULKAN_ENABLED
#include "core/variant/variant.h"
#include <drivers/vulkan/godot_vulkan.h>
const char *RenderingContextDriverVulkanAndroid::_get_platform_surface_extension() const {
@@ -47,7 +49,7 @@ RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAndroid::surface_c
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
VkResult err = vkCreateAndroidSurfaceKHR(instance_get(), &create_info, get_allocation_callbacks(VK_OBJECT_TYPE_SURFACE_KHR), &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
ERR_FAIL_COND_V_MSG(err != VK_SUCCESS, SurfaceID(), vformat("Couldn't create Android Surface (VkResult error %d).", err));
Surface *surface = memnew(Surface);
surface->vk_surface = vk_surface;
@@ -509,12 +509,6 @@ void DisplayServerWayland::_mouse_update_mode() {
wayland_thread.pointer_set_constraint(constraint);
if (wanted_mouse_mode == DisplayServerEnums::MOUSE_MODE_CAPTURED) {
WindowData *pointed_win = windows.getptr(wayland_thread.pointer_get_pointed_window_id());
ERR_FAIL_NULL(pointed_win);
wayland_thread.pointer_set_hint(pointed_win->rect.size / 2);
}
mouse_mode = wanted_mouse_mode;
}
@@ -1929,7 +1923,8 @@ void DisplayServerWayland::try_suspend() {
void DisplayServerWayland::process_events() {
wayland_thread.mutex.lock();
wayland_thread.keyboard_echo_keys();
// Some behavior depends on the progress of the main thread.
wayland_thread.main_loop_callback();
while (wayland_thread.has_message()) {
Ref<WaylandThread::Message> msg = wayland_thread.pop_message();
+113 -36
View File
@@ -401,7 +401,6 @@ void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {
seat_state_unlock_pointer(new_state);
wl_seat_current = p_seat;
pointer_set_constraint(pointer_constraint);
}
void WaylandThread::_window_hover(DisplayServerEnums::WindowID p_window_id) {
@@ -2110,6 +2109,19 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
}
if (ws != nullptr) {
if (hover_changed && pd.pointed_id != DisplayServerEnums::INVALID_WINDOW_ID) {
// We update the constraint only on enter.
wayland_thread->seat_state_unlock_pointer(ss);
if (wayland_thread->pointer_constraint == PointerConstraint::LOCKED) {
wayland_thread->seat_state_lock_pointer(ss, ws->wl_surface);
// Godot always expects a centered pointer when locked.
wayland_thread->seat_state_set_hint(ss, ws->rect.size.x / 2, ws->rect.size.y / 2);
} else if (wayland_thread->pointer_constraint == PointerConstraint::CONFINED) {
wayland_thread->seat_state_confine_pointer(ss, ws->wl_surface);
}
}
double scale = window_state_get_scale_factor(ws);
wayland_thread->_set_current_seat(ss->wl_seat);
@@ -2871,6 +2883,28 @@ void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_s
void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {
}
void WaylandThread::_zwp_locked_pointer_v1_on_locked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) {
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
ss->pointer_locked = true;
// Maybe by the time we lock the legacy constraint warp has already been
// committed. Let's give it a shot.
if (ss->constraint_warp_committed) {
PointerConstraint old_constraint = ss->wayland_thread->pointer_constraint;
ss->wayland_thread->pointer_set_constraint(PointerConstraint::NONE);
ss->wayland_thread->pointer_set_constraint(old_constraint);
ss->pointer_locked = false;
ss->constraint_warping = false;
ss->constraint_warp_committed = false;
}
}
void WaylandThread::_zwp_locked_pointer_v1_on_unlocked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) {
}
void WaylandThread::_wp_color_manager_on_supported_intent(void *data, struct wp_color_manager_v1 *wp_color_manager_v1, uint32_t render_intent) {
ColorManagementState *cms = (ColorManagementState *)data;
ERR_FAIL_NULL(cms);
@@ -4113,10 +4147,13 @@ void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {
zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);
p_ss->wp_confined_pointer = nullptr;
}
p_ss->pointer_locked = false;
}
void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
void WaylandThread::seat_state_lock_pointer(SeatState *p_ss, struct wl_surface *p_surface) {
ERR_FAIL_NULL(p_ss);
ERR_FAIL_NULL(p_surface);
if (p_ss->wl_pointer == nullptr) {
WARN_PRINT("Can't lock - no pointer?");
@@ -4128,15 +4165,12 @@ void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
return;
}
if (p_ss->wp_locked_pointer == nullptr) {
struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
if (locked_surface == nullptr) {
locked_surface = window_get_wl_surface(DisplayServerEnums::MAIN_WINDOW_ID);
}
ERR_FAIL_NULL(locked_surface);
p_ss->pointer_locked = false;
p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
// We always set a listener that tracks the locking status. This is useful as
// this constraint might be reused for the legacy warp hack.
p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, p_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
zwp_locked_pointer_v1_add_listener(p_ss->wp_locked_pointer, &zwp_locked_pointer_v1_listener, p_ss);
}
void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {
@@ -4162,8 +4196,9 @@ void WaylandThread::seat_state_warp_pointer(SeatState *p_ss, int p_x, int p_y) {
wp_pointer_warp_v1_warp_pointer(registry.wp_pointer_warp, surface, p_ss->wl_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y), p_ss->pointer_enter_serial);
}
void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
void WaylandThread::seat_state_confine_pointer(SeatState *p_ss, struct wl_surface *p_surface) {
ERR_FAIL_NULL(p_ss);
ERR_FAIL_NULL(p_surface);
if (p_ss->wl_pointer == nullptr) {
return;
@@ -4174,10 +4209,7 @@ void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
}
if (p_ss->wp_confined_pointer == nullptr) {
struct wl_surface *confined_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
ERR_FAIL_NULL(confined_surface);
p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, p_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
}
@@ -5378,19 +5410,22 @@ DisplayServerEnums::WindowID WaylandThread::pointer_get_last_pointed_window_id()
}
void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {
pointer_constraint = p_constraint;
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
ERR_FAIL_NULL(ss);
if (ss) {
WindowState *ws = window_get_state(ss->pointer_data.pointed_id);
if (ws) {
seat_state_unlock_pointer(ss);
if (p_constraint == PointerConstraint::LOCKED) {
seat_state_lock_pointer(ss);
} else if (p_constraint == PointerConstraint::CONFINED) {
seat_state_confine_pointer(ss);
if (pointer_constraint == PointerConstraint::LOCKED) {
seat_state_lock_pointer(ss, ws->wl_surface);
// Godot always expects a centered pointer when locked.
seat_state_set_hint(ss, ws->rect.size.x / 2, ws->rect.size.y / 2);
} else if (pointer_constraint == PointerConstraint::CONFINED) {
seat_state_confine_pointer(ss, ws->wl_surface);
}
}
pointer_constraint = p_constraint;
}
void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
@@ -5418,19 +5453,6 @@ void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
}
void WaylandThread::pointer_warp(const Point2i &p_to) {
// NOTE: This is for compositors that don't support the pointer-warp protocol.
// It's hacked together and not guaranteed to work.
if (registry.wp_pointer_warp == nullptr) {
PointerConstraint old_constraint = pointer_get_constraint();
pointer_set_constraint(PointerConstraint::LOCKED);
pointer_set_hint(p_to);
pointer_set_constraint(old_constraint);
return;
}
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (!ss) {
return;
@@ -5441,6 +5463,35 @@ void WaylandThread::pointer_warp(const Point2i &p_to) {
return;
}
// NOTE: This is for compositors that don't support the pointer-warp protocol.
// It's hacked together and not guaranteed to work.
if (registry.wp_pointer_warp == nullptr) {
if (ss->constraint_warping) {
return;
}
if (!ss->wp_locked_pointer) {
seat_state_unlock_pointer(ss);
seat_state_lock_pointer(ss, ws->wl_surface);
}
pointer_set_hint(p_to);
ss->constraint_warping = true;
ss->constraint_warp_committed = false;
if (!ss->wp_locked_pointer) {
seat_state_unlock_pointer(ss);
seat_state_lock_pointer(ss, ws->wl_surface);
}
pointer_set_hint(p_to);
// Gotta wait a frame... Not ideal but, y'know.
return;
}
// NOTE: It looks like it's not really recommended to convert from
// "godot-space" to "wayland-space" and in general I received mixed feelings
// discussing about this. I'm not really sure about the maths behind this but,
@@ -6133,6 +6184,32 @@ bool WaylandThread::is_fifo_available() const {
return registry.wp_fifo_manager_name != 0;
}
void WaylandThread::main_loop_callback() {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss) {
seat_state_echo_keys(ss);
// We need to wait a commit before "finalizing" the "legacy warp" hack. This
// callback is called *after* a commit, but we don't know when the warp
// request has been made, so we first wait one commit, then "finalize" on the
// next. Additionally, we also wait for the pointer to be locked, as otherwise
// it's not guaranteed for the position hint to even be acknowledged, AFAICT.
if (ss->constraint_warping && ss->pointer_locked) {
if (ss->constraint_warp_committed) {
PointerConstraint old_constraint = pointer_constraint;
pointer_set_constraint(PointerConstraint::NONE);
pointer_set_constraint(old_constraint);
ss->constraint_warping = false;
ss->constraint_warp_committed = false;
} else {
ss->constraint_warp_committed = true;
}
}
}
}
bool WaylandThread::is_suspended() const {
for (const KeyValue<DisplayServerEnums::WindowID, WindowState> &E : windows) {
if (!E.value.suspended) {
+17 -2
View File
@@ -508,6 +508,11 @@ public:
struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
bool pointer_locked = false;
bool constraint_warping = false;
bool constraint_warp_committed = false;
struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr;
// NOTE: According to the wp_pointer_gestures protocol specification, there
@@ -824,6 +829,9 @@ private:
static void _xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token);
// wayland-protocols event handlers.
static void _zwp_locked_pointer_v1_on_locked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1);
static void _zwp_locked_pointer_v1_on_unlocked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1);
static void _wp_color_manager_on_supported_intent(void *data, struct wp_color_manager_v1 *wp_color_manager_v1, uint32_t render_intent);
static void _wp_color_manager_on_supported_feature(void *data, struct wp_color_manager_v1 *wp_color_manager_v1, uint32_t feature);
static void _wp_color_manager_on_supported_tf_named(void *data, struct wp_color_manager_v1 *wp_color_manager_v1, uint32_t tf);
@@ -1027,6 +1035,11 @@ private:
};
// wayland-protocols event listeners.
static constexpr struct zwp_locked_pointer_v1_listener zwp_locked_pointer_v1_listener{
.locked = _zwp_locked_pointer_v1_on_locked,
.unlocked = _zwp_locked_pointer_v1_on_unlocked,
};
static constexpr struct wp_color_manager_v1_listener wp_color_manager_listener = {
.supported_intent = _wp_color_manager_on_supported_intent,
.supported_feature = _wp_color_manager_on_supported_feature,
@@ -1246,10 +1259,10 @@ public:
static EmbeddingCompositorState *godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor);
void seat_state_unlock_pointer(SeatState *p_ss);
void seat_state_lock_pointer(SeatState *p_ss);
void seat_state_lock_pointer(SeatState *p_ss, struct wl_surface *p_surface);
void seat_state_set_hint(SeatState *p_ss, int p_x, int p_y);
void seat_state_warp_pointer(SeatState *p_ss, int p_x, int p_y);
void seat_state_confine_pointer(SeatState *p_ss);
void seat_state_confine_pointer(SeatState *p_ss, struct wl_surface *p_surface);
static void seat_state_update_cursor(SeatState *p_ss);
@@ -1369,6 +1382,8 @@ public:
bool wait_frame_suspend_ms(int p_timeout);
bool is_fifo_available() const;
void main_loop_callback();
uint64_t window_get_last_frame_time(DisplayServerEnums::WindowID p_window_id) const;
bool window_is_suspended(DisplayServerEnums::WindowID p_window_id) const;
bool is_suspended() const;
+1 -1
View File
@@ -1689,7 +1689,7 @@ DisplayServerEnums::WindowID DisplayServerMacOS::create_sub_window(DisplayServer
DisplayServerEnums::WindowID id = _create_window(p_mode, p_vsync_mode, p_rect);
uint32_t set_flags = p_flags & ~(DisplayServerEnums::WINDOW_FLAG_MAX - 1); // Clear the flags that are not supported by the window.
uint32_t set_flags = p_flags & ((1 << DisplayServerEnums::WINDOW_FLAG_MAX) - 1); // Clear the flags that are not supported by the window.
while (set_flags != 0) {
// Find the index of the next set bit.
uint32_t index = (uint32_t)__builtin_ctzll(set_flags);
+260 -95
View File
@@ -81,6 +81,7 @@
#include <shlwapi.h>
#include <shobjidl.h>
#include <wbemcli.h>
#include <windns.h> // For QWORD.
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
@@ -287,6 +288,37 @@ void DisplayServerWindows::_set_mouse_mode_impl(DisplayServerEnums::MouseMode p_
SetCapture(wd.hWnd);
_register_raw_input_devices(window_id);
const BitField<WinKeyModifierMask> &mods = _get_mods();
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
mm->set_pressure(windows[window_id].last_pressure);
mm->set_ctrl_pressed(mods.has_flag(WinKeyModifierMask::CTRL));
mm->set_shift_pressed(mods.has_flag(WinKeyModifierMask::SHIFT));
mm->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT));
mm->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META));
mm->set_button_mask(mouse_get_button_state());
mm->set_position(center);
mm->set_global_position(center);
mm->set_relative(Vector2(0, 0));
mm->set_relative_screen_position(Vector2(0, 0));
mm->set_velocity(Vector2(0, 0));
mm->set_screen_velocity(Vector2(0, 0));
mm->set_global_position(center);
mm->set_velocity(Vector2(0, 0));
mm->set_screen_velocity(Vector2(0, 0));
if (windows[window_id].window_focused || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
old_x = center.x;
old_y = center.y;
}
} else {
// Mouse is free to move around (not captured or confined).
@@ -4221,6 +4253,174 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
return ret;
}
Vector2 DisplayServerWindows::_get_raw_mouse_motion(const RAWINPUT &p_raw, DisplayServerEnums::WindowID p_window_id) {
if (p_raw.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
return Vector2(p_raw.data.mouse.lLastX, p_raw.data.mouse.lLastY);
} else if (p_raw.data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
int n_screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int n_screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int n_screen_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
int n_screen_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
Vector2 abs_pos = Vector2(
(double(p_raw.data.mouse.lLastX) - 65536.0 / (n_screen_width)) * n_screen_width / 65536.0 + n_screen_left,
(double(p_raw.data.mouse.lLastY) - 65536.0 / (n_screen_height)) * n_screen_height / 65536.0 + n_screen_top);
POINT coords; // Client coords.
coords.x = abs_pos.x;
coords.y = abs_pos.y;
ScreenToClient(windows[p_window_id].hWnd, &coords);
Vector2 relative(coords.x - old_x, coords.y - old_y);
old_x = coords.x;
old_y = coords.y;
return relative;
}
return Vector2();
}
void DisplayServerWindows::_process_raw_mouse_motion(const Vector2 &p_relative, bool p_left_button_down, DisplayServerEnums::WindowID p_window_id) {
if (p_relative == Vector2()) {
return;
}
Ref<InputEventMouseMotion> mm;
mm.instantiate();
const BitField<WinKeyModifierMask> &mods = _get_mods();
mm->set_window_id(p_window_id);
mm->set_ctrl_pressed(mods.has_flag(WinKeyModifierMask::CTRL));
mm->set_shift_pressed(mods.has_flag(WinKeyModifierMask::SHIFT));
mm->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT));
mm->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META));
mm->set_pressure(p_left_button_down ? 1.0f : 0.0f);
mm->set_button_mask(mouse_get_button_state());
Point2i c(windows[p_window_id].width / 2, windows[p_window_id].height / 2);
// Centering just so it works as before.
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(windows[p_window_id].hWnd, &pos);
SetCursorPos(pos.x, pos.y);
mm->set_position(c);
mm->set_global_position(c);
mm->set_velocity(Vector2(0, 0));
mm->set_screen_velocity(Vector2(0, 0));
mm->set_relative(p_relative);
mm->set_relative_screen_position(p_relative);
if (windows[p_window_id].window_focused || windows[p_window_id].is_popup) {
Input::get_singleton()->parse_input_event(mm);
}
}
void DisplayServerWindows::_process_raw_input_event(const RAWINPUT &p_raw, DisplayServerEnums::WindowID p_window_id) {
if (p_raw.header.dwType == RIM_TYPEKEYBOARD) {
if (p_raw.data.keyboard.VKey == VK_SHIFT) {
// If multiple Shifts are held down at the same time,
// Windows natively only sends a KEYUP for the last one to be released.
// Handle all Shift "key up" events here for consistency.
if (p_raw.data.keyboard.Flags & RI_KEY_BREAK) {
ERR_FAIL_COND(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
const BitField<WinKeyModifierMask> &mods = _get_mods();
KeyEvent ke;
ke.shift = false;
ke.altgr = mods.has_flag(WinKeyModifierMask::ALT_GR);
ke.alt = mods.has_flag(WinKeyModifierMask::ALT);
ke.control = mods.has_flag(WinKeyModifierMask::CTRL);
ke.meta = mods.has_flag(WinKeyModifierMask::META);
ke.uMsg = WM_KEYUP;
ke.window_id = p_window_id;
ke.wParam = VK_SHIFT;
// data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift.
// Bit 30 -> key was previously down, bit 31 -> key is being released.
ke.lParam = p_raw.data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31;
key_event_buffer[key_event_pos++] = ke;
}
}
} else if (mouse_mode == DisplayServerEnums::MOUSE_MODE_CAPTURED && p_raw.header.dwType == RIM_TYPEMOUSE) {
_process_raw_mouse_motion(
_get_raw_mouse_motion(p_raw, p_window_id),
p_raw.data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN,
p_window_id);
}
}
void DisplayServerWindows::process_raw_input() {
if (!use_raw_input) {
return;
}
// Use the same window the mouse is captured for in _set_mouse_mode_impl(),
// which is not necessarily the main window.
DisplayServerEnums::WindowID window_id = _get_focused_window_or_popup();
if (!windows.has(window_id)) {
window_id = DisplayServerEnums::MAIN_WINDOW_ID;
}
// Normal 8 kHz frames stay precise; only a larger-sized tail that originates
// from a stall is coalesced.
constexpr UINT MAX_RAW_MOUSE_EVENTS_PER_FRAME = 512;
UINT raw_mouse_events = 0;
Vector2 coalesced_raw_mouse_motion;
bool coalesced_raw_mouse_left_button_down = false;
const bool coalesce_all_raw_mouse_motion = Input::get_singleton()->is_using_accumulated_input();
auto flush_coalesced_raw_mouse_motion = [&]() {
_process_raw_mouse_motion(coalesced_raw_mouse_motion, coalesced_raw_mouse_left_button_down, window_id);
coalesced_raw_mouse_motion = Vector2();
coalesced_raw_mouse_left_button_down = false;
};
// Read until the raw queue is empty. A small pass cap can never catch up
// after a long stall reaches Windows' 10,000-message queue limit, but a
// stall-sized captured mouse tail is coalesced below before it reaches the
// rest of the engine.
while (true) {
UINT n_buffer = 0;
// With a null buffer, this reports the byte size of the first pending
// message (the minimum required buffer), not a message count.
if (GetRawInputBuffer(nullptr, &n_buffer, sizeof(RAWINPUTHEADER)) != 0 || n_buffer == 0) {
flush_coalesced_raw_mouse_motion();
return;
}
UINT dw_size = n_buffer * sizeof(RAWINPUT);
LPBYTE lpb = new BYTE[dw_size];
// NOTE: Use dw_size, not n_buffer.
UINT n_read = GetRawInputBuffer((PRAWINPUT)lpb, &dw_size, sizeof(RAWINPUTHEADER));
if (n_read == (UINT)-1 || n_read == 0) {
delete[] lpb;
flush_coalesced_raw_mouse_motion();
return;
}
PRAWINPUT raw = (PRAWINPUT)lpb;
for (UINT i = 0; i < n_read; ++i) {
if (mouse_mode == DisplayServerEnums::MOUSE_MODE_CAPTURED &&
raw->header.dwType == RIM_TYPEMOUSE &&
(coalesce_all_raw_mouse_motion || raw_mouse_events >= MAX_RAW_MOUSE_EVENTS_PER_FRAME)) {
coalesced_raw_mouse_motion += _get_raw_mouse_motion(*raw, window_id);
coalesced_raw_mouse_left_button_down = coalesced_raw_mouse_left_button_down ||
(raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN);
} else {
_process_raw_input_event(*raw, window_id);
}
if (raw->header.dwType == RIM_TYPEMOUSE) {
raw_mouse_events++;
}
// Move to next RAWINPUT in buffer.
raw = NEXTRAWINPUTBLOCK(raw);
}
delete[] lpb;
}
}
void DisplayServerWindows::process_events() {
ERR_FAIL_COND(!Thread::is_main_thread());
@@ -4233,8 +4433,51 @@ void DisplayServerWindows::process_events() {
}
_THREAD_SAFE_LOCK_
process_raw_input();
// The pump throttles only what the hardware can flood, and drains the rest.
// See <https://ph3at.github.io/posts/Windows-Input/> for more information.
//
// - WM_INPUT is never dispatched from here: pulling raw input through
// PeekMessage() one message at a time is what collapses the pump at high
// polling rates (2,000 Hz and above). It stays in the queue for the next
// buffered read in process_raw_input().
// - WM_MOUSEMOVE and WM_NCMOUSEMOVE are synthesized on demand from the
// hardware input stream, so while the mouse is moving, the queue never
// reads empty and an unbounded pump spins until the mouse stops. They are
// dispatched at most once per frame each. No motion is lost: they carry
// the latest (coalesced) cursor position, and the relative motion derived
// from successive positions preserves the total delta. Captured mouse
// mode gets its sub-frame motion from raw input.
// - Everything else (keys, characters, mouse buttons, wheel, system
// messages) is discrete and low-rate, and is drained fully. Capping these
// would split the WM_CHAR posted by TranslateMessage() from its
// WM_KEYDOWN across frames, which breaks the pairing logic in
// _process_key_events() and makes keys fire twice.
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
auto peek_discrete = [&] {
BOOL ret = PeekMessageW(&msg, nullptr, 0, WM_NCMOUSEMOVE - 1, PM_REMOVE);
if (!ret) {
ret = PeekMessageW(&msg, nullptr, WM_NCMOUSEMOVE + 1, WM_INPUT - 1, PM_REMOVE);
}
if (!ret) {
ret = PeekMessageW(&msg, nullptr, WM_INPUT + 1, WM_MOUSEMOVE - 1, PM_REMOVE);
}
if (!ret) {
ret = PeekMessageW(&msg, nullptr, WM_MOUSEMOVE + 1, std::numeric_limits<UINT>::max(), PM_REMOVE);
}
return ret;
};
while (peek_discrete()) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (PeekMessageW(&msg, nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if (PeekMessageW(&msg, nullptr, WM_NCMOUSEMOVE, WM_NCMOUSEMOVE, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
@@ -5629,103 +5872,20 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
break;
}
// The bulk of raw input is drained in batch by process_raw_input()
// before the message pump runs. This only sees WM_INPUT messages the
// pump picks up afterwards (it peeks the full message range whenever
// the app is not focused, and raw input can also arrive while the pump
// itself is running). Without this fallback, DefWindowProc() would
// free their data unread.
UINT dwSize;
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER)) != 0 || dwSize == 0) {
break;
}
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[dwSize];
if (lpb == nullptr) {
return 0;
}
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
OutputDebugString(TEXT("GetRawInputData does not return correct size !\n"));
}
RAWINPUT *raw = (RAWINPUT *)lpb;
const BitField<WinKeyModifierMask> &mods = _get_mods();
if (raw->header.dwType == RIM_TYPEKEYBOARD) {
if (raw->data.keyboard.VKey == VK_SHIFT) {
// If multiple Shifts are held down at the same time,
// Windows natively only sends a KEYUP for the last one to be released.
if (raw->data.keyboard.Flags & RI_KEY_BREAK) {
// Make sure to check the latest key state since
// we're in the middle of the message queue.
if (GetAsyncKeyState(VK_SHIFT) < 0) {
// A Shift is released, but another Shift is still held
ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
KeyEvent ke;
ke.shift = false;
ke.altgr = mods.has_flag(WinKeyModifierMask::ALT_GR);
ke.alt = mods.has_flag(WinKeyModifierMask::ALT);
ke.control = mods.has_flag(WinKeyModifierMask::CTRL);
ke.meta = mods.has_flag(WinKeyModifierMask::META);
ke.uMsg = WM_KEYUP;
ke.window_id = window_id;
ke.wParam = VK_SHIFT;
// data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift.
// Bit 30 -> key was previously down, bit 31 -> key is being released.
ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31;
key_event_buffer[key_event_pos++] = ke;
}
}
}
} else if (mouse_mode == DisplayServerEnums::MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_window_id(window_id);
mm->set_ctrl_pressed(mods.has_flag(WinKeyModifierMask::CTRL));
mm->set_shift_pressed(mods.has_flag(WinKeyModifierMask::SHIFT));
mm->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT));
mm->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META));
mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f);
mm->set_button_mask(mouse_get_button_state());
Point2i c(windows[window_id].width / 2, windows[window_id].height / 2);
// Centering just so it works as before.
POINT pos = { (int)c.x, (int)c.y };
ClientToScreen(windows[window_id].hWnd, &pos);
SetCursorPos(pos.x, pos.y);
mm->set_position(c);
mm->set_global_position(c);
mm->set_velocity(Vector2(0, 0));
mm->set_screen_velocity(Vector2(0, 0));
if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY));
} else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN);
int nScreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN);
Vector2 abs_pos(
(double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft,
(double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop);
POINT coords; // Client coords.
coords.x = abs_pos.x;
coords.y = abs_pos.y;
ScreenToClient(hWnd, &coords);
mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y));
old_x = coords.x;
old_y = coords.y;
}
mm->set_relative_screen_position(mm->get_relative());
if ((windows[window_id].window_focused || windows[window_id].is_popup) && mm->get_relative() != Vector2()) {
Input::get_singleton()->parse_input_event(mm);
}
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize) {
_process_raw_input_event(*(RAWINPUT *)lpb, window_id);
}
delete[] lpb;
} break;
@@ -6614,6 +6774,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_SYSKEYUP:
case WM_KEYUP:
// Windows handles shift KEYUP inconsistently, handle with WM_INPUT
if (wParam == VK_SHIFT) {
break;
}
[[fallthrough]];
case WM_SYSKEYDOWN:
case WM_KEYDOWN: {
if (windows[window_id].ime_suppress_next_keyup && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) {
@@ -551,6 +551,10 @@ class DisplayServerWindows : public DisplayServer {
HWND _find_window_from_process_id(ProcessID p_pid, HWND p_current_hwnd);
void initialize_tts() const;
void process_raw_input();
Vector2 _get_raw_mouse_motion(const RAWINPUT &p_raw, DisplayServerEnums::WindowID p_window_id);
void _process_raw_mouse_motion(const Vector2 &p_relative, bool p_left_button_down, DisplayServerEnums::WindowID p_window_id);
void _process_raw_input_event(const RAWINPUT &p_raw, DisplayServerEnums::WindowID p_window_id);
struct ScreenHdrData {
bool hdr_supported = false;
+7
View File
@@ -68,6 +68,13 @@ char *wc_to_utf8(const wchar_t *wc) {
int widechar_main(int argc, wchar_t **argv) {
godot_init_profiler();
// Prevent Windows from replacing the window with a "ghost" when the main
// thread briefly stalls (e.g. during editor startup or focus transitions).
// The ghost window intercepts click-backs, so the editor never receives
// WM_ACTIVATEAPP and gets permanently stuck at the unfocused throttle.
// Must be called before any window is created.
DisableProcessWindowsGhosting();
OS_Windows os(nullptr);
setlocale(LC_CTYPE, "");
+6 -5
View File
@@ -43,9 +43,10 @@ void CollisionPolygon2D::_build_polygon() {
collision_object->shape_owner_clear_shapes(owner_id);
bool solids = build_mode == BUILD_SOLIDS;
const int polygon_size = polygon.size();
if (solids) {
if (polygon.size() < 3) {
if (polygon_size < 3) {
return;
}
@@ -59,19 +60,19 @@ void CollisionPolygon2D::_build_polygon() {
}
} else {
if (polygon.size() < 2) {
if (polygon_size < 2) {
return;
}
Ref<ConcavePolygonShape2D> concave = memnew(ConcavePolygonShape2D);
Vector<Vector2> segments;
segments.resize(polygon.size() * 2);
segments.resize(polygon_size * 2);
Vector2 *w = segments.ptrw();
for (int i = 0; i < polygon.size(); i++) {
for (int i = 0; i < polygon_size; i++) {
w[(i << 1) + 0] = polygon[i];
w[(i << 1) + 1] = polygon[(i + 1) % polygon.size()];
w[(i << 1) + 1] = polygon[(i + 1) % polygon_size];
}
concave->set_segments(segments);
+4 -3
View File
@@ -1288,10 +1288,11 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
GenProbesOctree octree;
octree.size = subdiv;
for (int i = 0; i < mesh_data.size(); i++) {
const int mesh_data_size = mesh_data.size();
for (int i = 0; i < mesh_data_size; i++) {
if (p_bake_step) {
float p = (float)(i) / mesh_data.size();
p_bake_step(0.3 + p * 0.1, vformat(RTR("Creating probes from mesh %d/%d"), i, mesh_data.size()), p_bake_userdata, false);
float p = (float)(i) / mesh_data_size;
p_bake_step(0.3 + p * 0.1, vformat(RTR("Creating probes from mesh %d/%d"), i, mesh_data_size), p_bake_userdata, false);
}
for (int j = 0; j < mesh_data[i].points.size(); j += 3) {
+2 -3
View File
@@ -251,8 +251,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
if (status.press_attempt && status.pressing_inside) {
if (toggle_mode) {
bool is_pressed = p_event->is_pressed();
if ((is_pressed && action_mode == ACTION_MODE_BUTTON_PRESS) || (!is_pressed && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (p_event->is_released() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
if (action_mode == ACTION_MODE_BUTTON_PRESS) {
status.press_attempt = false;
status.pressing_inside = false;
@@ -268,7 +267,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
queue_accessibility_update();
}
} else {
if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (p_event->is_released() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
_pressed();
}
}
@@ -1641,7 +1641,7 @@ void ParticlesStorage::update_particles() {
particles->instance_motion_vectors_last_change = frame;
// Copy particles to instance buffer.
if (particles->draw_order != RSE::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RSE::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RSE::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) {
if (particles->draw_order != RSE::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RSE::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RSE::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY && particles->transform_align != RSE::PARTICLES_TRANSFORM_ALIGN_LOCAL_BILLBOARD) {
//does not need view dependent operation, do copy here
ParticlesShader::CopyPushConstant copy_push_constant;
+1 -1
View File
@@ -2358,7 +2358,7 @@ void RendererSceneCull::_light_instance_setup_directional_shadow(int p_shadow_in
cull.shadows[p_shadow_index].cascades[i].split = distances[i + 1];
cull.shadows[p_shadow_index].cascades[i].shadow_texel_size = radius * 2.0 / texture_size;
cull.shadows[p_shadow_index].cascades[i].bias_scale = (z_max - z_min_cam);
cull.shadows[p_shadow_index].cascades[i].range_begin = z_max;
cull.shadows[p_shadow_index].cascades[i].range_begin = z_max - z_vec.dot(p_cam_transform.origin);
cull.shadows[p_shadow_index].cascades[i].uv_scale = uv_scale;
}
}
+2 -2
View File
@@ -972,7 +972,7 @@ public:
FUNC3(instance_geometry_set_shader_parameter, RID, const StringName &, const Variant &)
FUNC2RC(Variant, instance_geometry_get_shader_parameter, RID, const StringName &)
FUNC2RC(Variant, instance_geometry_get_shader_parameter_default_value, RID, const StringName &)
FUNC2C(instance_geometry_get_shader_parameter_list, RID, List<PropertyInfo> *)
FUNC2SC(instance_geometry_get_shader_parameter_list, RID, List<PropertyInfo> *)
FUNC3R(TypedArray<Image>, bake_render_uv2, RID, const TypedArray<RID> &, const Size2i &)
FUNC4R(PackedByteArray, bake_render_area_light_atlas, const TypedArray<RID> &, const TypedArray<Rect2> &, const Size2i &, int)
@@ -1059,7 +1059,7 @@ public:
FUNC3(canvas_item_set_instance_shader_parameter, RID, const StringName &, const Variant &)
FUNC2RC(Variant, canvas_item_get_instance_shader_parameter, RID, const StringName &)
FUNC2RC(Variant, canvas_item_get_instance_shader_parameter_default_value, RID, const StringName &)
FUNC2C(canvas_item_get_instance_shader_parameter_list, RID, List<PropertyInfo> *)
FUNC2SC(canvas_item_get_instance_shader_parameter_list, RID, List<PropertyInfo> *)
FUNC2(canvas_item_set_use_parent_material, RID, bool)
+1 -1
View File
@@ -8,7 +8,7 @@ readability.
## accesskit
- Upstream: https://github.com/AccessKit/accesskit-c
- Version: 0.21.2 (72460cd52d1b24492393cdf54a6f19d2fd63e258, 2026)
- Version: 0.22.3 (826d672661f9453c8b269ab3946dbcbae6300555, 2026)
- License: MIT
Files extracted from upstream source:
+233 -19
View File
@@ -19,6 +19,44 @@
#ifdef __ANDROID__
#include <jni.h>
#endif
#ifdef __APPLE__
#include <TargetConditionals.h>
/* Older SDKs may not define all of these; force them to 0 so the
value-based checks below are safe. */
#ifndef TARGET_OS_OSX
#define TARGET_OS_OSX 0
#endif
#ifndef TARGET_OS_IOS
#define TARGET_OS_IOS 0
#endif
#ifndef TARGET_OS_TV
#define TARGET_OS_TV 0
#endif
#ifndef TARGET_OS_WATCH
#define TARGET_OS_WATCH 0
#endif
#ifndef TARGET_OS_VISION
#define TARGET_OS_VISION 0
#endif
#ifndef TARGET_OS_MACCATALYST
#define TARGET_OS_MACCATALYST 0
#endif
#if TARGET_OS_OSX
#define ACCESSKIT_MACOS
#endif
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
#define ACCESSKIT_IOS
#endif
#if TARGET_OS_TV
#define ACCESSKIT_TVOS
#endif
#if TARGET_OS_WATCH
#define ACCESSKIT_WATCHOS
#endif
#if TARGET_OS_VISION
#define ACCESSKIT_VISIONOS
#endif
#endif /* __APPLE__ */
/**
* An action to be taken on an accessibility node.
@@ -592,15 +630,31 @@ typedef struct accesskit_android_queued_events accesskit_android_queued_events;
typedef struct accesskit_custom_action accesskit_custom_action;
#if defined(__APPLE__)
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
typedef struct accesskit_ios_adapter accesskit_ios_adapter;
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
typedef struct accesskit_ios_queued_events accesskit_ios_queued_events;
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
typedef struct accesskit_ios_subclassing_adapter
accesskit_ios_subclassing_adapter;
#endif
#if defined(ACCESSKIT_MACOS)
typedef struct accesskit_macos_adapter accesskit_macos_adapter;
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
typedef struct accesskit_macos_queued_events accesskit_macos_queued_events;
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
typedef struct accesskit_macos_subclassing_adapter
accesskit_macos_subclassing_adapter;
#endif
@@ -2561,7 +2615,167 @@ void accesskit_android_injecting_adapter_update_if_active(
void *update_factory_userdata);
#endif
#if defined(__APPLE__)
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Memory is also freed when calling this function.
*/
void accesskit_ios_queued_events_raise(
struct accesskit_ios_queued_events *events);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* This function must be called on the main thread.
* All handlers will always be called on the main thread.
*
* # Safety
*
* `view` must be a valid, unreleased pointer to a `UIView`.
*/
struct accesskit_ios_adapter *accesskit_ios_adapter_new(
void *view, accesskit_activation_handler_callback activation_handler,
void *activation_handler_userdata,
accesskit_action_handler_callback action_handler,
void *action_handler_userdata,
accesskit_deactivation_handler_callback deactivation_handler,
void *deactivation_handler_userdata);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
void accesskit_ios_adapter_free(struct accesskit_ios_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* You must call `accesskit_ios_queued_events_raise` on the returned pointer. It
* can be null if the adapter is not active.
*/
struct accesskit_ios_queued_events *accesskit_ios_adapter_update_if_active(
struct accesskit_ios_adapter *adapter,
accesskit_tree_update_factory update_factory,
void *update_factory_userdata);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Call this when the host view has just appeared on screen. If an
* assistive technology is running, this proactively builds the
* accessibility tree.
*
* You must call `accesskit_ios_queued_events_raise` on the returned pointer. It
* can be null if the adapter is not active.
*/
struct accesskit_ios_queued_events *accesskit_ios_adapter_view_did_appear(
struct accesskit_ios_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Returns whether the view itself is an accessibility element.
* This corresponds to `isAccessibilityElement`.
*/
bool accesskit_ios_adapter_is_accessibility_element(
struct accesskit_ios_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Returns a pointer to an `NSArray` of accessibility elements
* contained in the view. Ownership of the pointer is not transferred.
* This corresponds to `accessibilityElements`.
*/
void *accesskit_ios_adapter_accessibility_elements(
struct accesskit_ios_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Returns a pointer to the accessibility element at the specified point,
* or null if none. Ownership of the pointer is not transferred.
* This corresponds to `accessibilityHitTest:`.
*/
void *accesskit_ios_adapter_hit_test(struct accesskit_ios_adapter *adapter,
double x, double y);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* Caller must call `accesskit_string_free` with the return value.
*/
char *accesskit_ios_adapter_debug(const struct accesskit_ios_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* All handlers will always be called on the main thread.
*
* # Safety
*
* `view` must be a valid, unreleased pointer to a `UIView`.
*/
struct accesskit_ios_subclassing_adapter *accesskit_ios_subclassing_adapter_new(
void *view, accesskit_activation_handler_callback activation_handler,
void *activation_handler_userdata,
accesskit_action_handler_callback action_handler,
void *action_handler_userdata,
accesskit_deactivation_handler_callback deactivation_handler,
void *deactivation_handler_userdata);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* All handlers will always be called on the main thread.
*
* # Safety
*
* `window` must be a valid, unreleased pointer to a `UIWindow`.
*
* # Panics
*
* This function panics if the specified window doesn't currently have
* a root view controller with a view.
*/
struct accesskit_ios_subclassing_adapter *
accesskit_ios_subclassing_adapter_for_window(
void *window, accesskit_activation_handler_callback activation_handler,
void *activation_handler_userdata,
accesskit_action_handler_callback action_handler,
void *action_handler_userdata,
accesskit_deactivation_handler_callback deactivation_handler,
void *deactivation_handler_userdata);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
void accesskit_ios_subclassing_adapter_free(
struct accesskit_ios_subclassing_adapter *adapter);
#endif
#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \
defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS))
/**
* You must call `accesskit_ios_queued_events_raise` on the returned pointer. It
* can be null if the adapter is not active.
*/
struct accesskit_ios_queued_events *
accesskit_ios_subclassing_adapter_update_if_active(
struct accesskit_ios_subclassing_adapter *adapter,
accesskit_tree_update_factory update_factory,
void *update_factory_userdata);
#endif
#if defined(ACCESSKIT_MACOS)
/**
* Memory is also freed when calling this function.
*/
@@ -2569,7 +2783,7 @@ void accesskit_macos_queued_events_raise(
struct accesskit_macos_queued_events *events);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* # Safety
*
@@ -2581,11 +2795,11 @@ struct accesskit_macos_adapter *accesskit_macos_adapter_new(
void *action_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
void accesskit_macos_adapter_free(struct accesskit_macos_adapter *adapter);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* You must call `accesskit_macos_queued_events_raise` on the returned pointer.
* It can be null if the adapter is not active.
@@ -2596,7 +2810,7 @@ struct accesskit_macos_queued_events *accesskit_macos_adapter_update_if_active(
void *update_factory_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Update the tree state based on whether the window is focused.
*
@@ -2608,7 +2822,7 @@ accesskit_macos_adapter_update_view_focus_state(
struct accesskit_macos_adapter *adapter, bool is_focused);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Returns a pointer to an `NSArray`. Ownership of the pointer is not
* transferred.
@@ -2619,7 +2833,7 @@ void *accesskit_macos_adapter_view_children(
void *activation_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Returns a pointer to an `NSObject`. Ownership of the pointer is not
* transferred.
@@ -2630,7 +2844,7 @@ void *accesskit_macos_adapter_focus(
void *activation_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Returns a pointer to an `NSObject`. Ownership of the pointer is not
* transferred.
@@ -2641,7 +2855,7 @@ void *accesskit_macos_adapter_hit_test(
void *activation_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Caller must call `accesskit_string_free` with the return value.
*/
@@ -2649,7 +2863,7 @@ char *accesskit_macos_adapter_debug(
const struct accesskit_macos_adapter *adapter);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* # Safety
*
@@ -2663,7 +2877,7 @@ accesskit_macos_subclassing_adapter_new(
void *action_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* # Safety
*
@@ -2682,12 +2896,12 @@ accesskit_macos_subclassing_adapter_for_window(
void *action_handler_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
void accesskit_macos_subclassing_adapter_free(
struct accesskit_macos_subclassing_adapter *adapter);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* You must call `accesskit_macos_queued_events_raise` on the returned pointer.
* It can be null if the adapter is not active.
@@ -2699,7 +2913,7 @@ accesskit_macos_subclassing_adapter_update_if_active(
void *update_factory_userdata);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Update the tree state based on whether the window is focused.
*
@@ -2711,7 +2925,7 @@ accesskit_macos_subclassing_adapter_update_view_focus_state(
struct accesskit_macos_subclassing_adapter *adapter, bool is_focused);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Modifies the specified class, which must be a subclass of `NSWindow`,
* to include an `accessibilityFocusedUIElement` method that calls
@@ -2732,7 +2946,7 @@ void accesskit_macos_add_focus_forwarder_to_window_class(
const char *class_name);
#endif
#if defined(__APPLE__)
#if defined(ACCESSKIT_MACOS)
/**
* Modifies the specified class, which must be a subclass of `NSWindow`,
* to include an `accessibilityFocusedUIElement` method that calls