From c8f6a2e4d41bb0a605090215ae3d6c4b8400e21a Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 16 Sep 2025 09:46:19 +0300 Subject: [PATCH 1/6] implement setting a value on macOS --- platforms/macos/src/node.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 29cc4fce..c7cff8da 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -548,9 +548,14 @@ declare_class!( } #[method(setAccessibilityValue:)] - fn set_value(&self, _value: &NSObject) { - // This isn't yet implemented. See the comment on this selector - // in `is_selector_allowed`. + fn set_value(&self, value: &NSString) { + self.resolve_with_context(|node, context| { + context.do_action(ActionRequest { + action: Action::SetValue, + target: node.id(), + data: Some(ActionData::Value(value.to_string().into())), + }); + }); } #[method_id(accessibilityMinValue)] @@ -1024,11 +1029,7 @@ declare_class!( return node.supports_text_ranges(); } if selector == sel!(setAccessibilityValue:) { - // Our implementation of this currently does nothing, - // and it's not clear if VoiceOver ever actually uses it, - // but it must be allowed for editable text in order to get - // the expected VoiceOver behavior. - return node.supports_text_ranges() && !node.is_read_only(); + return node.supports_action(Action::SetValue, &filter) && !node.is_read_only(); } if selector == sel!(isAccessibilitySelected) { let wrapper = NodeWrapper(node); From 13eab054499a1a34e1f21f6a7ef38a0f8ffb7d48 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 16 Sep 2025 09:54:19 +0300 Subject: [PATCH 2/6] bring back previous selector allowed condition --- platforms/macos/src/node.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index c7cff8da..2f95978b 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -1029,7 +1029,10 @@ declare_class!( return node.supports_text_ranges(); } if selector == sel!(setAccessibilityValue:) { - return node.supports_action(Action::SetValue, &filter) && !node.is_read_only(); + return ( + node.supports_text_ranges() + || node.supports_action(Action::SetValue, &filter) + ) && !node.is_read_only(); } if selector == sel!(isAccessibilitySelected) { let wrapper = NodeWrapper(node); From e351dc24ccfa0f8161e8c405c041d3553509243b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 17 Sep 2025 14:57:48 +0300 Subject: [PATCH 3/6] also handle NSNumbers in setAccessibilityValue --- platforms/macos/src/node.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 2f95978b..ea17c6f1 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -23,8 +23,7 @@ use objc2::{ }; use objc2_app_kit::*; use objc2_foundation::{ - ns_string, NSArray, NSCopying, NSInteger, NSNumber, NSObject, NSPoint, NSRange, NSRect, - NSString, + ns_string, NSArray, NSCopying, NSInteger, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSString }; use std::rc::{Rc, Weak}; @@ -357,6 +356,12 @@ impl NodeWrapper<'_> { } } +// derived from objc2 0.6 `AnyObject::downcast_ref` +// TODO: can be removed after updating objc2 to 0.6 which has `AnyObject::downcast_ref` +fn downcast_ref(obj: &NSObject) -> Option<&T> { + obj.is_kind_of::().then(|| unsafe { &*(obj as *const NSObject).cast::() }) +} + pub(crate) struct PlatformNodeIvars { context: Weak, node_id: NodeId, @@ -548,14 +553,25 @@ declare_class!( } #[method(setAccessibilityValue:)] - fn set_value(&self, value: &NSString) { - self.resolve_with_context(|node, context| { - context.do_action(ActionRequest { - action: Action::SetValue, - target: node.id(), - data: Some(ActionData::Value(value.to_string().into())), + fn set_value(&self, value: &NSObject) { + dbg!(value.class()); + if let Some(string) = downcast_ref::(value) { + self.resolve_with_context(|node, context| { + context.do_action(ActionRequest { + action: Action::SetValue, + target: node.id(), + data: Some(ActionData::Value(string.to_string().into())), + }); }); - }); + } else if let Some(number) = downcast_ref::(value) { + self.resolve_with_context(|node, context| { + context.do_action(ActionRequest { + action: Action::SetValue, + target: node.id(), + data: Some(ActionData::NumericValue(number.doubleValue())), + }); + }); + } } #[method_id(accessibilityMinValue)] From c4e326c5f11735cca97755ec8fffe75dcc0cced3 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 18 Sep 2025 13:47:42 +0300 Subject: [PATCH 4/6] remove debug print --- platforms/macos/src/node.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index ea17c6f1..9b8d41a0 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -554,7 +554,6 @@ declare_class!( #[method(setAccessibilityValue:)] fn set_value(&self, value: &NSObject) { - dbg!(value.class()); if let Some(string) = downcast_ref::(value) { self.resolve_with_context(|node, context| { context.do_action(ActionRequest { From 3e357008b6c6118433117bc6a990e5c60b780d37 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 6 Oct 2025 12:22:16 +0300 Subject: [PATCH 5/6] `setAccessibilityValue` is valid only if the node supports Action::SetValue --- platforms/macos/src/node.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 9b8d41a0..0b3f332e 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -1044,10 +1044,7 @@ declare_class!( return node.supports_text_ranges(); } if selector == sel!(setAccessibilityValue:) { - return ( - node.supports_text_ranges() - || node.supports_action(Action::SetValue, &filter) - ) && !node.is_read_only(); + return node.supports_action(Action::SetValue, &filter); } if selector == sel!(isAccessibilitySelected) { let wrapper = NodeWrapper(node); From 264a7d85cc93b28c50d6bbee3a0bee01adc8ba1a Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 8 Oct 2025 14:53:10 +0300 Subject: [PATCH 6/6] format --- platforms/macos/src/node.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 0b3f332e..773f5668 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -23,7 +23,8 @@ use objc2::{ }; use objc2_app_kit::*; use objc2_foundation::{ - ns_string, NSArray, NSCopying, NSInteger, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSString + ns_string, NSArray, NSCopying, NSInteger, NSNumber, NSObject, NSObjectProtocol, NSPoint, + NSRange, NSRect, NSString, }; use std::rc::{Rc, Weak}; @@ -359,7 +360,8 @@ impl NodeWrapper<'_> { // derived from objc2 0.6 `AnyObject::downcast_ref` // TODO: can be removed after updating objc2 to 0.6 which has `AnyObject::downcast_ref` fn downcast_ref(obj: &NSObject) -> Option<&T> { - obj.is_kind_of::().then(|| unsafe { &*(obj as *const NSObject).cast::() }) + obj.is_kind_of::() + .then(|| unsafe { &*(obj as *const NSObject).cast::() }) } pub(crate) struct PlatformNodeIvars {