Closed mrclary closed 4 months ago
macOS file type association is a bit special. When you launch an application through an associated document, the OS triggers an event which the registered app must be able to listen to. In contrast, in Linux and Windows, the kernel simply launches {program} %s
through the command line arguments.
What we have done here is to implement a simple universal listener that will take the OS event and pass it to the handler via the designated command. In other words, you need event_handler
. I'm now realizing that the docs are missing this detail in the example. The tests showcase this a bit better.
macOS file type association is a bit special. When you launch an application through an associated document, the OS triggers an event which the registered app must be able to listen to. In contrast, in Linux and Windows, the kernel simply launches
{program} %s
through the command line arguments.What we have done here is to implement a simple universal listener that will take the OS event and pass it to the handler via the designated command. In other words, you need
event_handler
. I'm now realizing that the docs are missing this detail in the example. The tests showcase this a bit better.
I still don't understand why this is necessary. Following is a screencast illustrating that file associations work just fine with only the specifications in info.plist
. Is there something else that I'm missing here? What should be specified in the event_handler
? Just the same call to the application used for command
? Will this still result in a nested application bundle? Nevertheless, this breaks Spyder without even trying to open files.
This shows
CFBundleDocumentTypes
CFBundleDocumentTypes
, Spyder is not a recommended application for Python filesCFBundleDocumentTypes
, Spyder is a recommended application for Python filesinfo.plist
for Launch Services. This is only necessary for this demonstration because the info.plist
file is changed. Under normal circumstances, the info.plist
will be evaluated when the bundle is created in the Applications folder.Hm, when I was researching this I couldn't get it to work. Then @aganders3 gave us a hand with the listener/launcher. Is Spyder registering a special listening method in Qt? What version of macOS is this? Which Qt?
I think we can change the logic to inject the launcher (e.g. only when event_handler
is defined) if it works fine in some contexts.
Hm, I'm super interested. I thought maybe this could be done with Qt and/or pyobjc but I couldn't figure it out at the time. It would be great to not need this hack.
Edit: just peeked at the Spyder source. Maybe it's because Spyder uses itself as both the listener and handler?
Ah no there's some more customization here I need to understand
I think we can change the logic to inject the launcher (e.g. only when event_handler is defined) if it works fine in some contexts.
This is probably a reasonable way to do it, and would just need to document its then on the app itself to listen for the macOS events.
Yea, it looks like you are using applelaunchservices
to handle this via Qt.
Yea, it looks like you are using
applelaunchservices
to handle this via Qt.
applaunchservices
is not actually being used in this conda-based implementation; we explicitly do not import it if Spyder is in our constructor
-made environment. So the screencast I showed above does not use applaunchservices
. See here. We also bypass its import for our 5.x branch standalone application built with py2app
.
applaunchservices
was developed by one of Spyder's core developers long before I started working on standalone applications for it. It was intended to do just what you think it does, but for Spyder launched from command-line without an app bundle.
What I think may actually be going on is that this issue was resolved when we added the Python symbolic link to the app bundle, i.e. link_in_bundle
. (@jaimergp, I can't locate where we had that conversation in order to link back to it...) I think without the Python symbolic link, macOS sends the events to Python and not to the intended application (Spyder); hence the need for applaunchservices
or an event listener. With the Python symbolic link (and perhaps the executable stub?), macOS sends the events to the intended application and applaunchservices
or an event listener are not needed.
Hm, I'm super interested. I thought maybe this could be done with Qt and/or pyobjc but I couldn't figure it out at the time. It would be great to not need this hack.
Agreed.
Edit: just peeked at the Spyder source. Maybe it's because Spyder uses itself as both the listener and handler?
I don't believe so. See my previous comments about applaunchservices
.
Ah no there's some more customization here I need to understand
There is nothing magical going on here. We do handle FileOpen
events etc., but these are just the events that Qt receives. macOS must still send those events to Spyder before Qt can pick it up. If there is anything special that Qt is doing, then it does it regardless of whether Spyder is launched via app bundle or command-line and so I don't think is relevant to this discussion.
Thanks for the extra context. I'm not very familiar with Spyder but will take a closer look. I'm happy to arrange some time with either/both of you to chat live as well.
Okay I think I understand a bit more now. Qt is definitely doing whatever needs to be done here, and the only thing Spyder is doing is subclassing QApplication
to handle the QFileOpenEvent
(which Qt provides based on the OS event). This is great and I think will work for napari! It's just embarrassing that I missed it before and clearly overthought the problem 🤦.
Still I think the appkit-launcher we have may be useful for certain circumstances. Basically it would let you write a Python app without Qt and without importing any pyobjc stuff. I'm not sure how common that might be. I'm even less familiar with other GUI toolkits so not sure if they can similarly translate the macOS file open events. If it's not useful we can just toss that separate launcher and the corresponding logic for the nested app bundle.
Anyway for now let's try #183 (though I haven't looked closely at it) to see if that at least gets Spyder working.
The PR is ready for review. Let me know if it works or if the docs are clear! Thanks 🙏
Checklist
What happened?
When using
CFBundleDocumentTypes
for theosx
platform for Spyder, theinfo.plist
file is created correctly, but the application bundle does not launch. The resulting bundle structure is as follows:For Spyder, I don't believe that the sub application in Resources is necessary. If I omit
CFBundleDocumentTypes
from the json file, but later manually update theinfo.plist
file, then Spyder launches and behaves as expected.spyder-menu.json
``` { "$schema": "https://json-schema.org/draft-07/schema", "$id": "https://schemas.conda.io/menuinst-1.schema.json", "menu_name": "{{ DISTRIBUTION_NAME }} spyder", "menu_items": [ { "name": "Spyder __PKG_MAJOR_VER__ ({{ ENV_NAME }})", "description": "Scientific PYthon Development EnviRonment", "icon": "{{ MENU_DIR }}/spyder.{{ ICON_EXT }}", "activate": false, "terminal": false, "command": [""], "platforms": { "win": { "desktop": true, "app_user_model_id": "spyder.Spyder", "command": ["{{ PREFIX }}/pythonw.exe", "{{ PREFIX }}/Scripts/spyder-script.py"] }, "linux": { "Categories": [ "Development", "Science" ], "command": ["{{ PREFIX }}/bin/spyder", "$@"], "StartupWMClass": "Spyder" }, "osx": { "precommand": "pushd \"$(dirname \"$0\")\" &>/dev/null", "command": ["./python", "{{ PREFIX }}/bin/spyder", "$@"], "link_in_bundle": { "{{ PREFIX }}/bin/python": "{{ MENU_ITEM_LOCATION }}/Contents/MacOS/python" }, "CFBundleName": "Spyder __PKG_MAJOR_VER__", "CFBundleIdentifier": "org.spyder-ide.Spyder", "CFBundleVersion": "__PKG_VERSION__", "CFBundleDocumentTypes": [ { "CFBundleTypeName": "text document", "CFBundleTypeRole": "Editor", "LSHandlerRank": "Default", "CFBundleTypeIconFile": "spyder.icns", "LSItemContentTypes": [ "com.apple.applescript.text", "com.apple.ascii-property-list", "com.apple.audio-unit-preset", "com.apple.binary-property-list", "com.apple.configprofile", "com.apple.crashreport", "com.apple.dashcode.css", "com.apple.dashcode.javascript", "com.apple.dashcode.json", "com.apple.dashcode.manifest", "com.apple.dt.document.ascii-property-list", "com.apple.dt.document.script-suite-property-list", "com.apple.dt.document.script-terminology-property-list", "com.apple.property-list", "com.apple.rez-source", "com.apple.scripting-definition", "com.apple.structured-text", "com.apple.traditional-mac-plain-text", "com.apple.xcode.ada-source", "com.apple.xcode.apinotes", "com.apple.xcode.bash-script", "com.apple.xcode.configsettings", "com.apple.xcode.csh-script", "com.apple.xcode.entitlements-property-list", "com.apple.xcode.fortran-source", "com.apple.xcode.glsl-source", "com.apple.xcode.ksh-script", "com.apple.xcode.lex-source", "com.apple.xcode.make-script", "com.apple.xcode.mig-source", "com.apple.xcode.pascal-source", "com.apple.xcode.strings-text", "com.apple.xcode.tcsh-script", "com.apple.xcode.yacc-source", "com.apple.xcode.zsh-script", "com.apple.xml-property-list", "com.netscape.javascript-source", "com.scenarist.closed-caption", "com.sun.java-source", "com.sun.java-web-start", "net.daringfireball.markdown", "org.khronos.glsl-source", "org.oasis-open.xliff", "public.ada-source", "public.assembly-source", "public.bash-script", "public.c-header", "public.c-plus-plus-header", "public.c-plus-plus-source", "public.c-source", "public.case-insensitive-text", "public.comma-separated-values-text", "public.csh-script", "public.css", "public.delimited-values-text", "public.dylan-source", "public.filename-extension", "public.fortran-77-source", "public.fortran-90-source", "public.fortran-95-source", "public.fortran-source", "public.html", "public.json", "public.ksh-script", "public.lex-source", "public.log", "public.m3u-playlist", "public.make-source", "public.mig-source", "public.mime-type", "public.module-map", "public.nasm-assembly-source", "public.objective-c-plus-plus-source", "public.objective-c-source", "public.opencl-source", "public.pascal-source", "public.patch-file", "public.perl-script", "public.php-script", "public.plain-text", "public.python-script", "public.rss", "public.ruby-script", "public.script", "public.shell-script", "public.source-code", "public.tcsh-script", "public.text", "public.utf16-external-plain-text", "public.utf16-plain-text", "public.utf8-plain-text", "public.utf8-tab-separated-values-text", "public.xhtml", "public.xml", "public.yacc-source", "public.yaml", "public.zsh-script" ] } ] } } } ] } ```Conda Info
Conda Config
Conda list
Additional Context
No response