The Wayback Machine - https://web.archive.org/web/20211030072832/https://github.com/matplotlib/matplotlib/pull/14930
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

Set Dock icon in Qt5 and macosx backends on macOS #14930

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

6 participants
@joelfrederico
Copy link
Contributor

@joelfrederico joelfrederico commented Jul 30, 2019

PR Summary

This issue is similar to #14850/#14927. The issue is not isolated to the macos backend. It also happens in Qt5 (at least, possibly others).

Turns out Qt5 broke setWindowIcon. It seems like it is similar to this bug report: https://bugreports.qt.io/browse/QTBUG-55932. It fails silently, you request an icon change and nothing happens on macOS.

I have doubts this will be fixed without some pretty large Qt5 changes. Apple's API doesn't support .svg files. It does support a wide variety of other graphics, including vector graphics: pdf, and postscript, for instance. But Qt's API only supports .svg vector graphics.

We can work around this with png's, etc., but we already have a far superior .pdf logo. Apple users have come to expect high-quality vector graphics on their Retina displays. We should use the vector .pdf logo.

We can work around by using the ctypes lib to call Apple routines directly to avoid adding any more dependencies. It's done fairly lazily, so as not to be a performance hit on non-Apple platforms.

This appears to work for me. Could use comments and/or tests, although I have no idea how to write a unit test for an app icon in macOS.

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Jul 30, 2019

I'm not sure about unit tests. Could somebody provide feedback? I don't really have a way to unit-test any of the code, aside from perhaps checking that the ctypes/Apple api's say that I did, in fact, successfully change the icon. It sort of seems like circular reasoning: "Do the API's say they worked?"

At any rate, the icon is set for every plot on macOS with Qt5, so in a sense we are piggy-backing on those unit tests too.

Thoughts?

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Jul 30, 2019

I didn't see a reason to reset the icon, although that is also possible. Thoughts?

@tacaswell
Copy link
Member

@tacaswell tacaswell commented Jul 31, 2019

My knee-jerk reaction as that this is too much complexity for the problem it is solving.

From the linked bug report it suggests that the issue is that the at-conversion of svg -> QIcon does not provide a list of available sizes and then there is a bug in the OSX specific code that results in no icon being shown. Could we create a QIcon and set the missing size information?

Failing that, rendering a hi-dpi png (which we may already have due to the other backends) and using that seems like a better option.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Jul 31, 2019

I did play around with trying to get the QIcon to render/rasterize the SVG and failed. I can make another pass at it.

I did check for a high-res png and came up empty. None of them were large enough on my display. I wouldn't be surprised if the default on other backends were to use SVG, that's what Qt5 uses. I would recommend generating a larger logo if we go this route. I'm not sure how much larger- I'd want to do some math for render sizes when using a 5k monitor. Maybe just follow Apple's recommended icon sizes.

Neither are perfectly ideal if the system supports vector icons and we're providing a raster. From a performance standpoint, I'd say creating the logo from scratch is worst, rastering a SVG is better, loading a high-res png is even better, and loading a vector graphic is best.

I can also put this in the macos backend code instead of ctypes. If we need the code for the macos backend anyways, then I could expose the Objective-C functionality as an internal Python API for other backends to use. Of course this also violates encapsulation of the backends, which may or may not be a big deal here, and might require a Python wrapper so that other platforms don't complain about the missing macos module.

I can implement everything except for generating a new icon. I could probably figure that out, I'm just not sure of the process for doing that and adding a photo to the repo.

What would you like me to do?

@tacaswell
Copy link
Member

@tacaswell tacaswell commented Jul 31, 2019

I can also put this in the macos backend code instead of ctypes. If we need the code for the macos backend anyways, then I could expose the Objective-C functionality as an internal Python API for other backends to use. Of course this also violates encapsulation of the backends, which may or may not be a big deal here, and might require a Python wrapper so that other platforms don't complain about the missing macos module.

This seems like a promising path to explore.

generating (and caching) the png on the fly seems like a good option too.

@anntzer
Copy link
Contributor

@anntzer anntzer commented Jul 31, 2019

The icons are generated with tools/make_icons.py (#14941 may help a bit). You'll need to edit it to generate bigger sized icons.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 1, 2019

I can also put this in the macos backend code instead of ctypes. If we need the code for the macos backend anyways, then I could expose the Objective-C functionality as an internal Python API for other backends to use. Of course this also violates encapsulation of the backends, which may or may not be a big deal here, and might require a Python wrapper so that other platforms don't complain about the missing macos module.

This seems like a promising path to explore.

My last push went with this solution. If you like it, I'll add tests.

@joelfrederico joelfrederico changed the title Work around Qt5's bug/API issues setting Dock icon in macOS Set Dock icon in Qt5 and macosx backends on macOS Aug 1, 2019
@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 1, 2019

Wrote tests, fixed a few things. This only addresses the matplotlib icon not showing up on macosx and Qt5Agg backgrounds on macOS. I haven’t tested or worked on other backends.

src/_macosx.m Outdated Show resolved Hide resolved
src/_macosx.m Outdated Show resolved Hide resolved
src/_macosx.m Outdated Show resolved Hide resolved
src/_macosx.m Outdated Show resolved Hide resolved
@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 1, 2019

@anntzer Okay, pushed fixes, could use another review.

@anntzer
Copy link
Contributor

@anntzer anntzer commented Aug 2, 2019

Tagged the PR, I'm sure they'll show up :p

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 6, 2019

@anntzer Should we bump this somehow? Seems like nobody has noticed, but I'm not sure how long that usually takes here.

@jklymak
Copy link
Member

@jklymak jklymak commented Aug 7, 2019

I've been away, but wasn't popping this to the top of my queue because I wasn't clear that Matplotlib is an application, and hence am not clear why we would set the dock icon.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 7, 2019

@jklymak Honestly didn't consider it, since the Qt backends set the app icon on other platforms. Maybe the icon shouldn't be set in general?

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 7, 2019

But to be fair, they set the window icon and not the taskbar icon for e.g. Windows for me. I assumed this was a bug, because the icon is the Windows default blank page icon, which just seems like it was forgotten.

While macOS doesn't actually have a window icon...

I don't know!

@jklymak
Copy link
Member

@jklymak jklymak commented Aug 7, 2019

Yeah, I'm not saying this PR is wrong, just not sure what it should do an a Mac, or if its worth the maintenance headache if this goes awry down the line

pass


if sys.platform == "darwin":
Copy link
Member

@tacaswell tacaswell Aug 7, 2019

can you use pytest.skipif instead?

Copy link
Contributor Author

@joelfrederico joelfrederico Aug 7, 2019

Sure! Might be a bit before I can make a commit.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 7, 2019

@jklymak Apple has these guidelines:

App Icon

Beautiful, compelling icons are a fundamental part of the macOS user experience. Far from being merely decorative, icons play an essential role in communicating with users. To look at home in macOS, an app icon should be meticulously designed, informative, and aesthetically pleasing. It should convey the main purpose of the app and hint at the user experience.

App icons are associated with windows, which mean they should represent what the window's main purpose is. I think this is a solid argument for changing the app icon.

As a first pass, I think this PR is good. There is a slight complication where matplotlib may be embedded in another application. This is only relevant in some backends (IIRC the macosx backend cannot be embedded). In that case, the app icon should reflect the embedding application. That means we either need to detect embedding, or provide an API for using another app. This is probably a backend-specific consideration.

I don't think we should avoid improvements because of maintenance concerns... If it's a valid thing to do on macOS, if it makes sense and improves UX, it should be done right in order to minimize maintenance.

@tacaswell tacaswell added this to the v3.3.0 milestone Aug 8, 2019
@tacaswell
Copy link
Member

@tacaswell tacaswell commented Aug 8, 2019

I could have sworn I posted a comment earlier today but can not find it in this thread now (but it looks like @joelfrederico is responding to it!).

App icons are associated with windows

How does in know which window's icon to set? From the code it looks like this applies at the application level? If it is one per-application, can we do it just once in the module?

(IIRC the macosx backend cannot be embedded)

I don't actually know objective-c, but from skimming _macosx.m it looks like it follow the same pattern (a 'widget' for the canvas which is then embedded in a figure along with a toolbar). It may require writing a .m file, but I suspect it is possible to embed a matplotlib figure into a large native OSX application, but that is neither here nor there for this discussion.

Do tk, gtk, and wx have the same issue of lacking an icon?

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 8, 2019

App icons are associated with windows

How does in know which window's icon to set? From the code it looks like this applies at the application level? If it is one per-application, can we do it just once in the module?

Yes, it’s one icon per process. So we could do this just once, maybe even at the backend import level. I haven’t gotten too far into it, but right now matplotlib only has a Dock icon when a plot is visible. I don’t know how that works or why it was designed that way...

(IIRC the macosx backend cannot be embedded)

I don't actually know objective-c, but from skimming _macosx.m it looks like it follow the same pattern (a 'widget' for the canvas which is then embedded in a figure along with a toolbar). It may require writing a .m file, but I suspect it is possible to embed a matplotlib figure into a large native OSX application, but that is neither here nor there for this discussion.

I think this is only possible if you also embed python. But yes, now that I think about it, you could embed python and create matplotlib windows, which would then override the process’s icon. You might also be able to embed the widget... it depends on the matplotlib api, the current macosx backend may or may not have an api that makes this possible.

Do tk, gtk, and wx have the same issue of lacking an icon?

Ah I don’t know. I should probably make a more serious attempt at testing the various backends. Probably should also cache the original icon and reset when finished, whatever “finished” means.

@tacaswell
Copy link
Member

@tacaswell tacaswell commented Aug 8, 2019

I haven’t gotten too far into it, but right now matplotlib only has a Dock icon when a plot is visible. I don’t know how that works or why it was designed that way...

I suspect the doc-icon is generated when the QApplication (or the OSX backend equivalent) is instantiated. We are running a bit inverted, normally the first thing your process in a 'classic' desktop app is to instantiate the main "Application" instance (which in Qt you need to do before you can instantiate any of the widgets), however the user is usually running python first, and then at some point later when the user actually asks us for a window we fire up the qapp (and install the input hooks to make it look 'live' running inside your terminal) see https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_qt5.py#L98 and https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_qt5.py#L229 I suspect the easiest way to make sure we are not going to stomp on a user-set icon is to only try to set it if we create the qApp.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 8, 2019

I suspect the doc-icon is generated when the QApplication (or the OSX backend equivalent) is instantiated.

This is true for Qt. It's also possible to tell if Q(Gui)Application has been instantiated, at least in the PyQt5 backend. PyQt5.QtCore.QApplication.instance() == None if it has not been instantiated.

This is not true for macOS's NSApplication while running in Python. There is an API for switching application "activation policy" to a GUI mode. The Dock icon is generated at that point. It's also possible to tell if it's already in an activated mode via an adjacent API.

I think the right thing to do is first check if we're embedded. Is there already a QApplication, or are we in a GUI mode? If so, don't change anything.

If not, then cache the icon (this is independent of whether or not the icon is showing), if there is an icon to access. Then set our icon.

Then, there are various API's (Python C API and/or matplotlib code for macosx, Qt API for PyQt5) for running code once we've closed our windows. That code can access the cached icon (if any) and restore it.

How does that solution sound?

I'm not sure about other backends. I'd like to defer those until the backends I'm familiar with, macOS and PyQt5, are solid.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 8, 2019

FYI, from what I've seen, all of the backends I can test (Qt5Agg, macosx, GTK3Agg, TkAgg, WxAgg) currently have the same behavior (with the exception of WebAgg and nbAgg):

  • Backend uses Python icon
  • Backend does not turn off Python icon when all windows are closed
    • Unclear if this is because backends don't stop running when windows are closed
    • Edit: I know why: setActivationPolicy can only show icons and enable activation, not remove them or disable activation
    • There is a hack that changes LSUIElement, which is an info.plist entry, at runtime. It's ugly but apparently it works
    • I wouldn't recommend it, because if you forget to change it back (crash, whatever), the app icon is hidden, until you change it back
    • If it changes because of a crash, we can't guarantee that we'll be able to change it back
  • Icon is the Python rocketship

WebAgg does not show an icon at all. nbAgg is specifically for embedding in Jupyter notebook/lab, so showing an icon doesn't make sense.

Digression: FWIW, it looks like Qt4 is unsupported and a variety of package managers have dropped support for it. Perhaps matplotlib should drop Qt4 as a backend?

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 8, 2019

  • Edit: I know why: setActivationPolicy can only show icons and enable activation, not remove them or disable activation

Okay, so that's actually not true. You can change the policy for macOS 10.6+ to Prohibited or Regular: lionheart/openradar-mirror#16080.

Attempts to modify the application's activation policy. In OS X 10.9, any policy may be set; prior to 10.9, the activation policy may be changed to NSApplicationActivationPolicyProhibited or NSApplicationActivationPolicyRegular, but may not be changed to NSApplicationActivationPolicyAccessory. This returns YES if setting the activation policy is successful, and NO if not.

@joelfrederico
Copy link
Contributor Author

@joelfrederico joelfrederico commented Aug 14, 2019

FYI: I’ve discovered that the run loop integration isn’t handled very well, and there is quite a bit of cruft in the macOS Objective-C code. I have a consistent method for fixing all of these things, and integrating icon manipulation. In order for that all to make sense, though, I think there are a few cleanup tasks I’d like to get done. If I can tighten up the existing system, integrating icon manipulation will be a much more logical extension of the rest of the code.

This PR shouldn’t be merged yet, but I will rebase onto the changes that get accepted.

How does that sound?

@QuLogic QuLogic removed this from the v3.3.0 milestone May 2, 2020
@QuLogic QuLogic added this to the v3.4.0 milestone May 2, 2020
@jklymak jklymak marked this pull request as draft Jul 23, 2020
@QuLogic QuLogic removed this from the v3.4.0 milestone Jan 21, 2021
@QuLogic QuLogic added this to the v3.5.0 milestone Jan 21, 2021
@QuLogic QuLogic removed this from the v3.5.0 milestone Aug 23, 2021
@QuLogic QuLogic added this to the v3.6.0 milestone Aug 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment