Merge pull request #76829 from bruvzg/ac_kit_direct
Implement screen reader support using AccessKit library.
This commit is contained in:
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user