Merge pull request #76829 from bruvzg/ac_kit_direct

Implement screen reader support using AccessKit library.
This commit is contained in:
Thaddeus Crews
2025-04-08 12:32:47 -05:00
305 changed files with 32447 additions and 300 deletions

View File

@ -182,6 +182,18 @@ def configure(env: "SConsEnvironment"):
## Dependencies
if env["accesskit"]:
if env["accesskit_sdk_path"] != "":
env.Prepend(CPPPATH=[env["accesskit_sdk_path"] + "/include"])
if env["arch"] == "arm64" or env["arch"] == "universal":
env.Append(LINKFLAGS=["-L" + env["accesskit_sdk_path"] + "/lib/macos/arm64/static/"])
if env["arch"] == "x86_64" or env["arch"] == "universal":
env.Append(LINKFLAGS=["-L" + env["accesskit_sdk_path"] + "/lib/macos/x86_64/static/"])
env.Append(LINKFLAGS=["-laccesskit"])
else:
env.Append(CPPDEFINES=["ACCESSKIT_DYNAMIC"])
env.Append(CPPDEFINES=["ACCESSKIT_ENABLED"])
if env["builtin_libtheora"] and env["arch"] == "x86_64":
env["x86_libtheora_opt_gcc"] = True

View File

@ -427,6 +427,11 @@ public:
virtual void window_set_window_buttons_offset(const Vector2i &p_offset, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Vector3i window_get_safe_title_margins(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual int accessibility_should_increase_contrast() const override;
virtual int accessibility_should_reduce_animation() const override;
virtual int accessibility_should_reduce_transparency() const override;
virtual int accessibility_screen_reader_active() const override;
virtual Point2i ime_get_selection() const override;
virtual String ime_get_text() const override;

View File

@ -30,6 +30,7 @@
#import "display_server_macos.h"
#import "godot_application_delegate.h"
#import "godot_button_view.h"
#import "godot_content_view.h"
#import "godot_menu_delegate.h"
@ -52,6 +53,8 @@
#include <AppKit/AppKit.h>
#include "servers/rendering/dummy/rasterizer_dummy.h"
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
#endif
@ -60,6 +63,10 @@
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#endif
#if defined(ACCESSKIT_ENABLED)
#include "drivers/accesskit/accessibility_driver_accesskit.h"
#endif
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#import <IOKit/IOCFPlugIn.h>
@ -68,14 +75,15 @@
#import <IOKit/hid/IOHIDLib.h>
DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) {
WindowID id;
const float scale = screen_get_max_scale();
WindowID id = window_id_counter;
{
WindowData wd;
WindowData &wd = windows[id];
wd.window_delegate = [[GodotWindowDelegate alloc] init];
ERR_FAIL_NULL_V_MSG(wd.window_delegate, INVALID_WINDOW_ID, "Can't create a window delegate");
[wd.window_delegate setWindowID:window_id_counter];
[wd.window_delegate setWindowID:id];
int rq_screen = get_screen_from_rect(p_rect);
if (rq_screen < 0) {
@ -100,12 +108,15 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
backing:NSBackingStoreBuffered
defer:NO];
ERR_FAIL_NULL_V_MSG(wd.window_object, INVALID_WINDOW_ID, "Can't create a window");
[wd.window_object setWindowID:window_id_counter];
[wd.window_object setWindowID:id];
[wd.window_object setReleasedWhenClosed:NO];
wd.window_view = [[GodotContentView alloc] init];
ERR_FAIL_NULL_V_MSG(wd.window_view, INVALID_WINDOW_ID, "Can't create a window view");
[wd.window_view setWindowID:window_id_counter];
if (wd.window_view == nil) {
windows.erase(id);
ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Can't create a window view");
}
[wd.window_view setWindowID:id];
[wd.window_view setWantsLayer:TRUE];
[wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
@ -115,6 +126,16 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
[wd.window_object setRestorable:NO];
[wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]];
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver && !accessibility_driver->window_create(id, (__bridge void *)wd.window_object)) {
if (OS::get_singleton()->is_stdout_verbose()) {
ERR_PRINT("Can't create an accessibility adapter for window, accessibility support disabled!");
}
memdelete(accessibility_driver);
accessibility_driver = nullptr;
}
#endif
if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) {
[wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
}
@ -156,23 +177,43 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
}
#endif
Error err = rendering_context->window_create(window_id_counter, &wpd);
#ifdef ACCESSKIT_ENABLED
if (err != OK && accessibility_driver) {
accessibility_driver->window_destroy(id);
}
#endif
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s context", rendering_driver));
rendering_context->window_set_size(window_id_counter, p_rect.size.width, p_rect.size.height);
rendering_context->window_set_vsync_mode(window_id_counter, p_vsync_mode);
}
#endif
#if defined(GLES3_ENABLED)
bool gl_failed = false;
if (gl_manager_legacy) {
Error err = gl_manager_legacy->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context.");
if (err != OK) {
gl_failed = true;
}
}
if (gl_manager_angle) {
CALayer *layer = [(NSView *)wd.window_view layer];
Error err = gl_manager_angle->window_create(window_id_counter, nullptr, (__bridge void *)layer, p_rect.size.width, p_rect.size.height);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context.");
if (err != OK) {
gl_failed = true;
}
}
window_set_vsync_mode(p_vsync_mode, window_id_counter);
if (gl_failed) {
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver) {
accessibility_driver->window_destroy(id);
}
#endif
windows.erase(id);
ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Can't create an OpenGL context.");
}
window_set_vsync_mode(p_vsync_mode, id);
#endif
[wd.window_view updateLayerDelegate];
@ -184,10 +225,8 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
offset.y = (nsrect.origin.y + nsrect.size.height);
offset.y -= (windowRect.origin.y + windowRect.size.height);
[wd.window_object setFrameTopLeftPoint:NSMakePoint(wpos.x - offset.x, wpos.y - offset.y)];
id = window_id_counter++;
windows[id] = wd;
}
window_id_counter++;
WindowData &wd = windows[id];
window_set_mode(p_mode, id);
@ -762,6 +801,8 @@ bool DisplayServerMacOS::get_is_resizing() const {
}
void DisplayServerMacOS::window_destroy(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
#if defined(GLES3_ENABLED)
if (gl_manager_legacy) {
gl_manager_legacy->window_destroy(p_window);
@ -775,6 +816,11 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) {
if (rendering_context) {
rendering_context->window_destroy(p_window);
}
#endif
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver) {
accessibility_driver->window_destroy(p_window);
}
#endif
windows.erase(p_window);
@ -835,6 +881,11 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE:
case FEATURE_EMOJI_AND_SYMBOL_PICKER:
return true;
#ifdef ACCESSKIT_ENABLED
case FEATURE_ACCESSIBILITY_SCREEN_READER: {
return (accessibility_driver != nullptr);
} break;
#endif
default: {
}
}
@ -3031,6 +3082,22 @@ DisplayServer::VSyncMode DisplayServerMacOS::window_get_vsync_mode(WindowID p_wi
return DisplayServer::VSYNC_ENABLED;
}
int DisplayServerMacOS::accessibility_should_increase_contrast() const {
return [(GodotApplicationDelegate *)[[NSApplication sharedApplication] delegate] getHighContrast];
}
int DisplayServerMacOS::accessibility_should_reduce_animation() const {
return [(GodotApplicationDelegate *)[[NSApplication sharedApplication] delegate] getReduceMotion];
}
int DisplayServerMacOS::accessibility_should_reduce_transparency() const {
return [(GodotApplicationDelegate *)[[NSApplication sharedApplication] delegate] getReduceTransparency];
}
int DisplayServerMacOS::accessibility_screen_reader_active() const {
return [(GodotApplicationDelegate *)[[NSApplication sharedApplication] delegate] getVoiceOver];
}
Point2i DisplayServerMacOS::ime_get_selection() const {
return im_selection;
}
@ -3624,6 +3691,7 @@ Vector<String> DisplayServerMacOS::get_rendering_drivers_func() {
drivers.push_back("opengl3");
drivers.push_back("opengl3_angle");
#endif
drivers.push_back("dummy");
return drivers;
}
@ -3815,6 +3883,16 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
native_menu = memnew(NativeMenuMacOS);
#ifdef ACCESSKIT_ENABLED
if (accessibility_get_mode() != DisplayServer::AccessibilityMode::ACCESSIBILITY_DISABLED) {
accessibility_driver = memnew(AccessibilityDriverAccessKit);
if (accessibility_driver->init() != OK) {
memdelete(accessibility_driver);
accessibility_driver = nullptr;
}
}
#endif
NSMenuItem *menu_item;
NSString *title;
@ -4006,6 +4084,10 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
}
force_process_and_drop_events();
if (rendering_driver == "dummy") {
RasterizerDummy::make_current();
}
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
RasterizerGLES3::make_current(true);
@ -4074,7 +4156,11 @@ DisplayServerMacOS::~DisplayServerMacOS() {
rendering_context = nullptr;
}
#endif
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver) {
memdelete(accessibility_driver);
}
#endif
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr);
CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr);

View File

@ -35,9 +35,20 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
- (void)activate;
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate> {
bool high_contrast;
bool reduce_motion;
bool reduce_transparency;
bool voice_over;
}
- (void)forceUnbundledWindowActivationHackStep1;
- (void)forceUnbundledWindowActivationHackStep2;
- (void)forceUnbundledWindowActivationHackStep3;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- (void)accessibilityDisplayOptionsChange:(NSNotification *)notification;
- (bool)getHighContrast;
- (bool)getReduceMotion;
- (bool)getReduceTransparency;
- (bool)getVoiceOver;
@end

View File

@ -143,14 +143,55 @@
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(system_theme_changed:) name:@"AppleColorPreferencesChangedNotification" object:nil];
}
static const char *godot_ac_ctx = "gd_accessibility_observer_ctx";
- (id)init {
self = [super init];
[[NSWorkspace sharedWorkspace] addObserver:self forKeyPath:@"voiceOverEnabled" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:(void *)godot_ac_ctx];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(accessibilityDisplayOptionsChange:) name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification object:nil];
high_contrast = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldIncreaseContrast];
reduce_motion = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion];
reduce_transparency = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency];
voice_over = [[NSWorkspace sharedWorkspace] isVoiceOverEnabled];
return self;
}
- (void)dealloc {
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleInterfaceThemeChangedNotification" object:nil];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleColorPreferencesChangedNotification" object:nil];
[[NSWorkspace sharedWorkspace] removeObserver:self forKeyPath:@"voiceOverEnabled" context:(void *)godot_ac_ctx];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (void *)godot_ac_ctx) {
voice_over = [[NSWorkspace sharedWorkspace] isVoiceOverEnabled];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)accessibilityDisplayOptionsChange:(NSNotification *)notification {
high_contrast = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldIncreaseContrast];
reduce_motion = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion];
reduce_transparency = [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceTransparency];
}
- (bool)getHighContrast {
return high_contrast;
}
- (bool)getReduceMotion {
return reduce_motion;
}
- (bool)getReduceTransparency {
return reduce_transparency;
}
- (bool)getVoiceOver {
return voice_over;
}
- (void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {

View File

@ -324,6 +324,9 @@
wd.focused = true;
ds->set_last_focused_window(window_id);
#ifdef ACCESSKIT_ENABLED
ds->accessibility_set_window_focused(window_id, true);
#endif
ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_IN);
}
@ -341,6 +344,9 @@
wd.focused = false;
ds->release_pressed_events();
#ifdef ACCESSKIT_ENABLED
ds->accessibility_set_window_focused(window_id, false);
#endif
ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_OUT);
}
@ -354,6 +360,9 @@
wd.focused = false;
ds->release_pressed_events();
#ifdef ACCESSKIT_ENABLED
ds->accessibility_set_window_focused(window_id, false);
#endif
ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_OUT);
}
@ -368,6 +377,9 @@
if ([wd.window_object isKeyWindow]) {
wd.focused = true;
ds->set_last_focused_window(window_id);
#ifdef ACCESSKIT_ENABLED
ds->accessibility_set_window_focused(window_id, true);
#endif
ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_FOCUS_IN);
}
}