On Sat, May 19, 2018 at 04:42:54PM +1200, Nick Helm wrote:
> On Sat, 19 May 2018 at 07:36:32 +1200, Alan Third wrote:
>
> > There are two problems, though.
> >
> > * Application Menu
> >
> > When the last NS frame is deleted the menus aren’t updated, so I think
> > they’ll be the menus as defined for the last frame. They should
> > probably be cut right back to just the ‘Emacs’ menu, and perhaps
> > ‘help’.
>
> This is interesting. I think we could tweak the EmacsMenu side of things
> to do this, even with the current code. We'd want to make sure to
> include some kind of new frame command somewhere.
>
> These menus are derived dynamically from Lisp though, and it might be
> difficult to convince it to provide valid entries when no frame is
> selected.
I’m not sure if the ‘Emacs’ menu is derived from lisp. If you turn off
menus completely it still exists.
> One way around that might be to create a new menu containing
> static NSMenu versions of File and Help, much like we do for the appMenu
> now. When no frame is selected and there are no Lisp menu entries, we
> switch mainMenu to this new menu. When a frame is created, we switch
> back.
Perhaps we could modify ns_update_menubar to handle the case where
there’s no frame?
> > * Dock Menu
> >
> > This has a ‘new frame’ option but just plain doesn’t work. I think the
> > problem is the way it’s trying to create a new frame:...
> >
> > This seems to rely on there being an existing NS frame, but we’ve
> > deleted the last one. I assume if we were clever we’d be able to use
> > the, still open, terminal frame.
>
> With all of this discussion, I feel like I'm missing something really
> fundamental. Putting emacsclient aside for a moment, if we were dealing
> with a standard Cocoa app, the window server and NSApp loop would keep
> running in the background and we could close everything, then create a
> new frame (as a view on an NSWindow instance) as needed.
FWIW, the NSApp loop does keep running, but it’s embedded in ns_select
and ns_read_socket. We can, in theory, create a new frame directly,
but it needs to run through Emacs first.
> How is the architecture of Emacs on NS so different from standard Cocoa
> that these kinds of workarounds are necessary?
The fundamental issue in the NS port is that we have two bosses: the
NSApp run loop, and Emacs lisp, and we need to mediate between them.
A standard Cocoa app responds to events from the NSApp loop, and
that’s pretty much it. As I understand it you can make a complete app
without very much work this way. The NSApp loop is the boss.
Emacs’ architecture expects everything to go through lisp and it makes
decisions on what we do with our GUI. Emacs lisp is the boss.
We can’t react directly to NS events like a normal NS app. We get
input from the NSApp loop and send it to lisp. Lisp might tell us to
do something any time. We also need to know how Emacs expects to do
things (the GUI code was originally written for a completely different
platform after all) and then try and make Cocoa/GNUstep handle it
correctly.
In an ideal world (and I believe the Mac port has gone this way) we
would put the NSApp run loop in one thread, and Emacs lisp in another
and let them communicate with each other asynchronously. This wouldn’t
solve everything, but it would make some of our problems easier.
We can’t easily do that, though, as the inter‐thread communication
systems provided in Objective‐C are either a pain to implement with
complex types like Lisp_Object, or aren’t compatible with GCC and/or
GNUstep (Grand Central Dispatch).
Additionally I think we sometimes have workarounds fixing workarounds
fixing hacks, all written by different people, and the simplest
solution is to strip it right back to the basics. I have a feeling the
menus fall somewhat into this category, and the drag‐n‐drop stuff
definitely does.
In an ideal world (and I believe the Mac port has gone this way) we
TL;DR: Emacs internal architecture doesn’t fit well into the standard
NS application architecture.
FWIW, this problem description and the solution seem the same on every OS/toolkit: the Lisp interpreter should always run in a background thread and communicate with the UI thread asynchronously through message passing.