Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Various cursors cannot be displayed on macOS #3724

Closed
atbentley opened this issue Jun 10, 2024 · 7 comments · Fixed by #4033
Closed

Various cursors cannot be displayed on macOS #3724

atbentley opened this issue Jun 10, 2024 · 7 comments · Fixed by #4033
Labels
B - bug Dang, that shouldn't have happened DS - macos

Comments

@atbentley
Copy link

Description

Running the window example and pressing CTRL+C to cycle through the available cursors reveals that not all cursors can be displayed.

When a cursor cannot be displayed the following warning is emitted and the cursor reverts to the default cursor.

WARN winit::platform_impl::platform::cursor: cursor `_windowResizeSouthWestCursor` appears to be invalid

Here is the log of relevant events from the window example:

INFO window: Setting cursor to "Wait"
WARN winit::platform_impl::platform::cursor: cursor `busyButClickableCursor` appears to be invalid
INFO window: Setting cursor to "Help"
WARN winit::platform_impl::platform::cursor: cursor `_helpCursor` appears to be invalid
INFO window: Setting cursor to "Progress"
WARN winit::platform_impl::platform::cursor: cursor `busyButClickableCursor` appears to be invalid
INFO window: Setting cursor to "ZoomIn"
WARN winit::platform_impl::platform::cursor: cursor `_zoomInCursor` appears to be invalid
INFO window: Setting cursor to "ZoomOut"
WARN winit::platform_impl::platform::cursor: cursor `_zoomOutCursor` appears to be invalid
INFO window: Setting cursor to "NeResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeNorthEastCursor` appears to be invalid
INFO window: Setting cursor to "NwResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeNorthWestCursor` appears to be invalid
INFO window: Setting cursor to "SeResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeSouthEastCursor` appears to be invalid
INFO window: Setting cursor to "SwResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeSouthWestCursor` appears to be invalid
INFO window: Setting cursor to "NeswResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeNorthEastSouthWestCursor` appears to be invalid
INFO window: Setting cursor to "NwseResize"
WARN winit::platform_impl::platform::cursor: cursor `_windowResizeNorthWestSouthEastCursor` appears to be invalid

macOS version

ProductName:		macOS
ProductVersion:		14.1.1
BuildVersion:		23B81

Winit version

0.30.0

@atbentley atbentley added B - bug Dang, that shouldn't have happened DS - macos labels Jun 10, 2024
@killercup
Copy link
Contributor

killercup commented Jun 10, 2024

Can reproduce on macOS 14.4.1 and a checkout of 3a624e0 -- RUST_LOG=warn cargo run --example window gives warnings for these cursors:

  • _helpCursor
  • busyButClickableCursor
  • _zoomInCursor
  • _zoomOutCursor
  • _windowResizeNorthEastCursor
  • _windowResizeNorthWestCursor
  • _windowResizeSouthEastCursor
  • _windowResizeSouthWestCursor
  • _windowResizeNorthEastSouthWestCursor
  • _windowResizeNorthWestSouthEastCursor

@voidburn
Copy link

Can reproduce on macOS Sequoia 15.1 on 0.30.5. Is there any hope to see this addressed? I'll be available to help test alternative implementations.

2024-11-19T08:21:46.953359Z  WARN winit::platform_impl::macos::cursor: cursor '_helpCursor' appears to be invalid
2024-11-19T08:21:47.416072Z  WARN winit::platform_impl::macos::cursor: cursor 'busyButClickableCursor' appears to be invalid
2024-11-19T08:21:51.509492Z  WARN winit::platform_impl::macos::cursor: cursor '_zoomInCursor' appears to be invalid
2024-11-19T08:21:51.905360Z  WARN winit::platform_impl::macos::cursor: cursor '_zoomOutCursor' appears to be invalid
2024-11-19T08:21:52.889228Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeNorthEastCursor' appears to be invalid
2024-11-19T08:21:53.232156Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeNorthWestCursor' appears to be invalid
2024-11-19T08:21:53.898392Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeSouthEastCursor' appears to be invalid
2024-11-19T08:21:54.245413Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeSouthWestCursor' appears to be invalid
2024-11-19T08:21:55.527909Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeNorthEastSouthWestCursor' appears to be invalid
2024-11-19T08:21:55.912475Z  WARN winit::platform_impl::macos::cursor: cursor '_windowResizeNorthWestSouthEastCursor' appears to be invalid

As stated here: https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview/46635398#46635398 WebKit contains images that look exactly the same as the cursors used by the system, maybe we could replace them with custom cursors for the macOS platform?

@slice
Copy link
Contributor

slice commented Dec 8, 2024

The frame resize, column resize, row resize, and zoom cursors became public API in macOS Sequoia (15.0):

These are accessible via:

These don't seem to be exposed in objc2-app-kit at the moment.

Though, strangely enough, trying to access these cursors privately via Swift still works:

2024-12-08_111248AM_PLuLBsqn@2x

Digging into this more, winit uses AnyClass::responds_to to check if the given private cursor exists:

if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {
tracing::warn!("cursor `{sel}` appears to be invalid");
None
}

This invokes the class_respondsToSelector function from the ObjC runtime, and I believe the Swift code is performing a respondsToSelector on the NSCursor class instead.

Apple's guidance seems to generally recommend the latter as well:

You should usually use NSObject's respondsToSelector: or instancesRespondToSelector: methods instead of this function.

We can send that message from Rust:

use objc2::{msg_send, sel, ClassType};
use objc2_app_kit::*;

fn main() {
    let private_cursor_sel = sel!(_windowResizeNorthEastCursor);

    println!(
        "responds via class_respondsToSelector? {}",
        NSCursor::class().responds_to(private_cursor_sel)
    );

    let responds: bool =
        unsafe { msg_send![NSCursor::class(), respondsToSelector: private_cursor_sel] };
    println!("responds via -[NSObject respondsToSelector:]? {}", responds);
}

Running this code on my system (currently macOS 15.1.1, 24B91) yields:

responds via class_respondsToSelector? false
responds via -[NSObject respondsToSelector:]? true

FWICT, there doesn't seem to be a non-unsafe way to send respondsToSelector: to the class itself. Looking at the interfaces and protcols alone, this shouldn't seem possible, but Apple added an exemption to NSObject, as it is a root class.

From the relevant section in Apple's documentation archive (there's a relevant StackOverflow question, too):

So that NSObject methods don’t have to be implemented twice—once to provide a runtime interface for instances and again to duplicate that interface for class objects—class objects are given special dispensation to perform instance methods defined in the root class. When a class object receives a message that it can’t respond to with a class method, the runtime system determines whether there’s a root instance method that can respond. The only instance methods that a class object can perform are those defined in the root class, and only if there’s no class method that can do the job.

So, despite respondsToSelector: being an instance method, it also works on the class objects of NSObject subclasses.

This is probably what winit should be calling instead of .responds_to, which might also fix these cursors not displaying. If there is a more fluent way to send respondsToSelector: to the NSCursor class, then it should be preferred. cc @madsmtm

@madsmtm
Copy link
Member

madsmtm commented Dec 8, 2024

Thanks for the detailed investigation!

You are right that we should be using respondsToSelector: - and in the future, objc2::available! (once I release the next version) to select the new methods when available.

And yeah, they're not exposed in the current version of objc2-app-kit, as that is only updated for Xcode 15.4 (macOS 14.5 SDK), see the docs. The yet unreleased next version of objc2-app-kit does have them. In the meantime, we could use msg_send_id! to work around it.

I'd accept a PR implementing this ;)

@slice
Copy link
Contributor

slice commented Dec 8, 2024

I'd accept a PR implementing this ;)

@madsmtm Thanks for the insight. I'm interested in helping! Just to clarify, by "this" do you mean sending respondsToSelector: inside of winit to check private cursor availability, or adding the msg_send_id! workaround? This quick patch for the former does the trick, and I've locally validated that the cursors begin working again (with the window example, at least):

diff --git a/src/platform_impl/apple/appkit/cursor.rs b/src/platform_impl/apple/appkit/cursor.rs
index 70a36e11..b8befe11 100644
--- a/src/platform_impl/apple/appkit/cursor.rs
+++ b/src/platform_impl/apple/appkit/cursor.rs
@@ -4,7 +4,7 @@ use std::sync::OnceLock;
 
 use objc2::rc::Retained;
 use objc2::runtime::Sel;
-use objc2::{msg_send_id, sel, ClassType};
+use objc2::{msg_send, msg_send_id, sel, ClassType};
 use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
 use objc2_foundation::{
     ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
@@ -67,7 +67,7 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
 
 unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
     let cls = NSCursor::class();
-    if cls.responds_to(sel) {
+    if msg_send![cls, respondsToSelector: sel] {
         let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
         Some(cursor)
     } else {

@madsmtm
Copy link
Member

madsmtm commented Dec 8, 2024

The answer is yes to both ;)

@madsmtm
Copy link
Member

madsmtm commented Dec 8, 2024

I've opened #4034 for using the documented cursors

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
B - bug Dang, that shouldn't have happened DS - macos
Development

Successfully merging a pull request may close this issue.

5 participants