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

macOS: Notes on notification detection #47

Closed
madsmtm opened this issue Oct 8, 2024 · 18 comments · Fixed by #50 or #60
Closed

macOS: Notes on notification detection #47

madsmtm opened this issue Oct 8, 2024 · 18 comments · Fixed by #50 or #60

Comments

@madsmtm
Copy link
Contributor

madsmtm commented Oct 8, 2024

The current detection mechanism just runs detect in a loop, which is woefully inefficient. Details on more efficient implementation options in #37 (comment) and https://github.com/cormacrelf/dark-notify/blob/master/src/app.rs.

Also, it doesn't actually work, see #29. I suspect that the problem is that we need to be running the main application with NSApplication::run or NSApplicationMain for this to work? But semantically, that probably isn't really something that rust-dark-light should be doing, so unsure what to do.

Even if that isn't the problem, or it can be worked around by using "AppleInterfaceThemeChangedNotification", another problem is that most Rust async runtimes don't support mach ports, and as such wouldn't update since notifications arrive via. those, I think? See madsmtm/objc2#279 for that.

@Be-ing
Copy link
Collaborator

Be-ing commented Oct 9, 2024

Hmm, I'm wondering if we should make the async API platform-specific until someone steps up to implement proper async support for every OS...

@casperstorm
Copy link

Also, it spikes the CPU to maximum as soon as running the notify example.

@madsmtm
Copy link
Contributor Author

madsmtm commented Jan 1, 2025

Hmm, I don't think #50 fixes the polling issue? It just moves the polling to a separate thread that busy loops instead...

@edfloreshz
Copy link
Collaborator

I'll see if I can apply the suggestions today, I'll open this again.

@edfloreshz edfloreshz reopened this Jan 1, 2025
@casperstorm
Copy link

Running the notify example also still spikes CPU to 100% and memory keeps going up.

Screenshot 2025-01-06 at 23 11 42 Screenshot 2025-01-06 at 23 12 03

@Be-ing
Copy link
Collaborator

Be-ing commented Jan 6, 2025

I think we should release 2.0 with async being platform-specific then.

@edfloreshz
Copy link
Collaborator

@madsmtm I tried your suggestions, but I'm not very familiar with Apple APIs. I'm very interested in implementing this, though. Would you mind if I picked your brain a little?

@edfloreshz
Copy link
Collaborator

edfloreshz commented Jan 7, 2025

I managed to get it working, however, the only reliable way I found requires creating an application and running it, effectively blocking the main thread.

Is there a way to do it without running the app?

It appears that winit already has a working implementation of this for macOS and Windows, but it requires creating a window to listen for events, blocking the main thread as well.

@madsmtm
Copy link
Contributor Author

madsmtm commented Jan 7, 2025

Well, if it was easy to fix I'd have done it myself ;)

Is there a way to do it without running the app?

I don't know, did you try the "AppleInterfaceThemeChangedNotification" notification? And did you try it with the different ways of running the event loop (dispatch_main, NSRunLoop::run or NSApplication::run)?

If you can make it work with dispatch_main, then I potentially see a (long) way forwards, otherwise I think the only way is for rust-dark-light to just implement the observer, and then document that the user should be running the event loop themselves.

@Be-ing
Copy link
Collaborator

Be-ing commented Jan 7, 2025

If you can make it work with dispatch_main, then I potentially see a (long) way forwards, otherwise I think the only way is for rust-dark-light to just implement the observer, and then document that the user should be running the event loop themselves.

I'm not familiar with Objective C or how to work with macOS's platform APIs directly. Are you suggesting that dark_light could provide a helper function that would return some struct to the caller that the caller would then be responsible for running on whatever event loop the caller has running on their side?

@edfloreshz
Copy link
Collaborator

edfloreshz commented Jan 7, 2025

It looks like winit already has an event to detect the theme change, though it only works in macOS and Windows, instead of trying to figure out how to make this work we could help implement Linux support for that specific event and any Rust toolkit that is based on winit (which is most of them) can leverage that to provide its own way to handle event changes.

That would leave this crate only responsible for detection and not notification.

@madsmtm
Copy link
Contributor Author

madsmtm commented Jan 7, 2025

Are you suggesting that dark_light could provide a helper function that would return some struct to the caller that the caller would then be responsible for running on whatever event loop the caller has running on their side?

We could reasonably provide the following API:

struct NotificationListener(...);

impl Drop for NotificationListener {
    // Unregister the notification
}

/// Note: For this to trigger, you must have the application event loop running.
fn listen(callback: impl Fn(Mode) + Send + 'static) -> NotificationListener {
    // Register notification listener
}

That would leave this crate only responsible for detection and not notification.

That's a good compromise, I think. Preferably with documentation in the crate docs itself that notification detection isn't possible cross-platform, and what the user should do instead.

@Be-ing
Copy link
Collaborator

Be-ing commented Jan 7, 2025

It looks like winit already has an event to detect the theme change, though it only works in macOS and Windows, instead of trying to figure out how to make this work we could help implement Linux support for that specific event and any Rust toolkit that is based on winit (which is most of them) can leverage that to provide its own way to handle event changes.

I looked at the implementation of this in winit for Wayland and it's not what it seems. The Theme struct in winit only affects the client-side decoration that winit draws via the sctk-adwaita crate. It has nothing to do with the system dark mode preference. There's a Window::with_theme method which the documentation says can use dbus to find the system theme, but that's false; the strings dbus, zbus, and ashpd don't appear anywhere else in the winit repository.

@edfloreshz
Copy link
Collaborator

I was actually referring to WindowEvent::ThemeChanged.

@Be-ing
Copy link
Collaborator

Be-ing commented Jan 8, 2025

winit also has a Window::theme API to query light/dark mode at any time, but it doesn't support Linux. I forgot that back when @edfloreshz and I were implementing the initial Linux support in this crate, I commented on a winit issue about that and suggested that perhaps winit should remove that API. I still think that a windowing system abstraction is a weird place to query a system preference, so I dug back to the initial PR that added it to winit to understand why it is there. It's there because Windows needs to query the light/dark mode preference to tell the window system whether to make the window's title bar light or dark. It turns out Wayland also needs it for the same reason because winit needs to draw client-side decoration for a titlebar on GNOME (because Mutter developers refuse to support server-side decoration 🤦 ) Though winit doesn't actually handle that for CSD on its own, the sctk-adwaita crate that winit uses for CSD implemented that on its own calling out to dbus-send, leaving winit with this API that says it does the thing but actually does nothing 🙃 😩

And, judging by @madsmtm's comments here (please correct me if I misunderstand), the only way to get light/dark mode updates on macOS is for it to be handled by the windowing crate (and maybe by the downstream application?) but it's not possible for an intermediate library like this that is neither the windowing crate nor the end user application to handle.

😵

So where do we go from here? Should this crate continue to exist, or should we deprecated and tell people to use winit's API? But then we'd need to make the winit API actually work on Linux... which I don't look forward to, considering a winit maintainer has already expressed opposition to. Plus, it would be difficult to use zbus in winit because winit isn't an async executor so using zbus in winit would require running an executor in a separate thread and passing data back to the main thread. While most Rust GUI applications use winit, it's not universal, so... maybe it would make sense for this crate to exist independently, and adapt it so winit can use this crate internally?

This is such a messy situation for something that should be quite simple. Just looking at Slint for example, they first used this crate, then removed it citing compile times, then of course users complained, then they used ashpd on their own to fill in the gap where winit doesn't work despite winit working for other platforms.

@edfloreshz
Copy link
Collaborator

If you ask me, the sanest approach would be to remove subscribe and simply remain as a detection crate, it would be up to GUI authors to rely on window creation libraries to detect theme changes.

I would've loved to have that in this library but it just doesn't appear to be a feasible option due to platforms specific limitations.

What do you think @Be-ing?

@Be-ing
Copy link
Collaborator

Be-ing commented Jan 9, 2025

What do you think @Be-ing?

That might be a reasonable way forward.

I just noticed @madsmtm is a contributor to winit, so I'm curious to hear your thoughts on this.

@madsmtm
Copy link
Contributor Author

madsmtm commented Jan 9, 2025

I just noticed @madsmtm is a contributor to winit, so I'm curious to hear your thoughts on this.

I'm not familiar with Linux enough to comment on whether an implementation of ThemeChanged and system_theme makes sense there, sorry :/.

But it seems like you have a working implementation, so I'd suggest you open an issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants