Skip to content
New issue

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

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

Already on GitHub? Sign in 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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