RFC: macOS Command-Q behavior

53 views
Skip to first unread message

Albrecht Schlosser

unread,
Jan 29, 2018, 10:56:14 AM1/29/18
to fltk.coredev
All,

after a long discussion in fltk.general in thread "Terminating an app on
OSX" I decided to open a new thread in fltk.coredev for (hopefully)
final discussion and a decision how to proceed. Link to the mentioned
thread in fltk.general:

https://groups.google.com/d/msg/fltkgeneral/CI01CBNm9KY/-mauUlVTAAAJ

I apologize for the long posting, but please read, discuss, and maybe vote.

Summary:

The OP (Mathieu Peyréga) reported that destructors of global objects
were not executed if he quitted the program with Command-Q under macOS.
This is caused by the current Command-Q handling in FLTK under macOS in
Fl_Cocoa.mm which uses NSTerminateNow in the reply to the macOS
Command-Q handling function.

We discussed about adding an option to change this behavior or a handler
that would be called from the internal Command-Q processing in FLTK but
Manolo explained that the application can use a global keyboard shortcut
to intercept the Command-Q handling which makes the use of another
handler obsolete (although it would IMHO still have some advantages).

Since both ways to intercept Command-Q would need code changes I agreed
with Manolo (and Ian who wrote "I am inclined to agree") that a new
handler is not necessary. But...

The remaining important issue is compatibility with existing programs.

[End of Summary]

It is IMHO really bad behavior if a normal user can shortcut normal
program behavior by using an OS-provided Command-Q function that will
"kill" unmodified programs with unexpected results. The system menu of
macOS adds the Command-Q shortcut so every user can use it even if the
programmer did not support it as a global shortcut. Its menu label is
"Quit the app" or similar (I don't know because I don't have a Mac
available to test). Note that it is not called "Abort" or "Kill" so user
expectation would be to do a clean exit.

The current behavior is to send FL_CLOSE events to all windows and, if
all windows were closed, reply NSTerminateNow to the macOS Command-Q
handler. This leads to an immediate exit of the program with obviously
these facts (as Manolo explained and demonstrated with code):

(1) functions registered with atexit() are executed (OK)
(2a) Fl::run() or equivalent never returns (BAD)
(2b) hence code after Fl::run() is never executed (BAD)
(3) global destructors are not executed (BAD)

Manolo explained how programs can be coded to do their jobs with
termination code in atexit() handlers and/or in Fl_Window callbacks. But
this is not sufficient because you'd have to change programs to work as
expected.

As we often discussed we want to avoid necessary program changes because
often users that didn't write the original program and want to use it on
newer OS's or port it usually don't have enough knowledge to change the
program accordingly.

The current behavior of Command-Q handling under macOS in FLTK
definitely leads to unexpected results. Users and programmers can have a
hard time to find out why this is so (see above: 2a,2b, and 3).

I believe the only thing we *really* need to change is the return value
that FLTK's Command-Q returns to the OS. Manolo suggested in
fltk.general to use NSTerminateCancel instead of NSTerminateNow in a
modified library, and the OP (Mathieu Peyréga) replied:

> I confirm that changing line 1505 of Fl_cocoa.mm from
>
> NSApplicationTerminateReply reply = NSTerminateNow;
>
> to
>
> NSApplicationTerminateReply reply = NSTerminateCancel;
>
> in order to have a behaviour where the Fl::run() loop exit normaly and
> main keeps running until the end.
>
> I would really appreciate if a global function is added to mainstream
> version in order to switch from one behaviour to the other.

After all the discussion I believe that changing this in the FLTK
library is essential for compatibility and to avoid really unexpected
and bad program behavior if a user uses the Command-Q ("Quit the app.")
menu.

I attach a test program that demonstrates my concerns. Unfortunately I
can't test it under MacOS, so I request other devs that have access to a
Mac to test it with the usage of Command-Q under macOS and post comments
here. The program outputs some lines to stdout so you'd probably have to
run it from a console window or such. It *should* output something like
this:

-Info- MyWindow constructor.
-Info- Starting main.
-Info- Fl_Button: exit_cb() - closing main window.
*IMPORTANT* After Fl::run(): closing files, terminating worker threads...
-Info- Returning from main.
*IMPORTANT* MyWindow destructor.

Lines marked with "-Info-" are informational and can vary depending on
how you terminate the program.

Lines marked with *IMPORTANT* _must_ be present. The test program uses a
button callback, a window callback, cleanup code after Fl::run(), and
finally a global (static) MyWindow object with a destructor.

Note that the MyWindow class is only an example for any global (static)
object with destructor.

It is a strong convention that global destructors will be executed after
the program returns from main(). Although this may often not necessary
to free resources allocated by the OS it may be necessary for a program
that relies on this behavior. It is a legitimate requirement to rely on
this behavior in a C++ program.

That said, if the program does not output the two *IMPORTANT* lines when
terminated by the user with Command-Q (or the sytem menu) then I request
that we change FLTK's behavior. This can easily be done by changing the
return value as discussed above to NSTerminateCancel.

What would happen if we changed it?

Every program that doesn't close all windows on FL_CLOSE events, maybe
by issuing a prompt or popup window and the user denying termination, is
unaffected.

Every "normal", unmodified program (like my attached demo) that closes
all windows when it receives FL_CLOSE events would return from
Fl::run(), do its cleanup tasks after Fl::run(), exit main() and run
destructors.

Only programs that do "strange" things after Fl::run() would maybe
continue to run (I posted example code in fltk.general), but this can be
considered rare exceptions. Only such programs would need to be changed
to intercept Command-Q with a global keyboard shortcut to do a clean
shutdown when Command-Q is typed by the user.

I'd really appreciate if you tested the Command-Q behavior with the
attached program.

If it behaves as (bad as) expected my vote would be to change
NSTerminateNow to NSTerminateCancel and that's it. It should then behave
well. Please test this as well on a Mac.

Thanks in advance.
hello_with_dtor.cxx

Manolo

unread,
Jan 29, 2018, 1:20:59 PM1/29/18
to fltk.coredev

========== output after closing the window by click on "click to exit" button or on window close button  ========

-Info- MyWindow constructor.
-Info- Starting main.
-Info- Fl_Button: exit_cb() - closing main window.
*IMPORTANT* After Fl::run(): closing files, terminating worker threads...
-Info- Returning from main.
*IMPORTANT* MyWindow destructor.
================================================


========== output after stopping app with cmd-Q  ========

-Info- MyWindow constructor.
-Info- Starting main.
-Info- MyWindow: win_cb() - closing main window.
*IMPORTANT* MyWindow destructor.
================================================

As previously discussed, the present cmd-Q processing terminates
the app before Fl::run() would return, thus "*IMPORTANT* After Fl::run():"
does not appear.

In my view, it all boils down to what is considered a "normal" program.

Under non MacOS, a normal GUI program should cleanly stop
when all its windows are closed, because there's no way to
interact with it through its GUI.

What is a normal MacOS program expected to do when all its
windows close?
Firefox, MSWord, Mail, Xcode, XQuartz, etc.. keep running
because, through their system menu bar, it's possible to open a
new window.

Therefore, cmd-Q does more than close all windows, it also terminates
these programs.

What do we want a normal FLTK application to do on MacOS when all windows close?
1) exit after having run the cleanup code placed after the Fl::run() statement,
if there's one?

or

2) allow the possibility to keep running (which requires to have put something
in the application's system menu bar) ?

Choosing 1) means that applications behave identically across platforms,
and requires to change in FLTK the present processing of cmd-Q, so Fl::run()
returns after cmd-Q is processed.

Choosing 2) means that the standard behaviour of a Mac app is obtained,
with the present FLTK code, putting
#if __APPLE__
  while (true) Fl::wait();
#endif
towards the end of main().

If we would change the processing of cmd-Q to stop it from terminating the
app and instead terminate the event loop, then a MacOS app wanting to run
with no open window would have to register a cmd-Q handler
as a global shortcut and make it do what FLTK used to do.

With today's code, FLTK behaves unexpectedly only if its cleanup code does not find
naturally its place in window callbacks. If it does, closing windows by cmd-Q
calls the callbacks and the cleanup code runs.

If an app is conceived so that ctrl-Q or cmd-Q stop it under
all platforms,  it is necessary to define a global shortcut and to make it
cleanly stop the app, most probably by stopping Fl:run() and running code
placed later. This is done with only platform-independent code,
and requires no change to FLTK, because FLTK's handling of cmd-Q does not run.

Another possibility is that cmd-Q is handled by the app only on the MacOS platform.
My reasoning is that, in that case, cmd-Q is expected to terminate the program for good
when all windows replied to the FL_CLOSE event by closing themselves, because that's
what most MacOS app do.
A consequence would be that all cleanup code should be placed in window callbacks
or in atexit() functions.

I am ready to conclude this reasoning is too MacOS inspired if it creates difficulties
for FLTK developers.

Albrecht Schlosser

unread,
Jan 29, 2018, 3:40:39 PM1/29/18
to fltkc...@googlegroups.com
On 29.01.2018 19:20 Manolo wrote:
>
> ========== output after closing the window by click on "click to exit"
> button or on window close button  ========
> -Info- MyWindow constructor.
> -Info- Starting main.
> -Info- Fl_Button: exit_cb() - closing main window.
> *IMPORTANT* After Fl::run(): closing files, terminating worker threads...
> -Info- Returning from main.
> *IMPORTANT* MyWindow destructor.
> ================================================

Okay, that's what I expected.

> ========== output after stopping app with cmd-Q  ========
> -Info- MyWindow constructor.
> -Info- Starting main.
> -Info- MyWindow: win_cb() - closing main window.
> *IMPORTANT* MyWindow destructor.
> ================================================

I'm surprised that "MyWindow destructor" is executed when Cmd-Q is
pressed. Maybe I misinterpreted the OP's message and he didn't mean
destructors of _global_ variables but destructors of _local_ variables
in main(). This was probably not clear in his report.

Manolo and others, could you please test the new attached program with
Cmd-Q on macOS? TIA.

This one has an additional local (stack) variable (class MyButton) with
a destructor. This should display something like:

-Info- MyWindow constructor: global
-Info- Starting main.
-Info- MyButton constructor: local in main()
-Info- MyWindow: win_cb() - closing main window
*IMPORTANT* After Fl::run(): closing files, terminating worker threads...
-Info- Returning from main.
*IMPORTANT* MyButton destructor: local in main()
*IMPORTANT* MyWindow destructor: global

Note that we now have three *IMPORTANT* lines. Given the test results
above from Manolo (thank you!) and the OP's report I assume that we
won't see the line

*IMPORTANT* MyButton destructor: local in main()

if the program is terminated with Cmd-Q.

> As previously discussed, the present cmd-Q processing terminates
> the app before Fl::run() would return, thus "*IMPORTANT* After Fl::run():"
> does not appear.

Yep, that was to be expected - and I consider this a bug!

> In my view, it all boils down to what is considered a "normal" program.

Sorry, I can't agree. In my view it all boils down to two questions:

(1) Are destructors executed when the user types Cmd-Q?

(2) Do *valid* *existing* programs still work as expected and as they do
on other platforms when Cmd-Q is typed.

Question (1) will hopefully be answered by a test with my modified demo
program. If destructors (in this case: of local variables in main()) are
not executed, then this is a bug and we should NOT do this.

Question (2) is more difficult to answer. I wrote my demo program
because I think that this is a valid FLTK program. I don't think that it
is unusual to have code after Fl::run().

Side note (not entirely serious, but true): even our simple hello.cxx
has code after Fl::run() because it reads:

return Fl::run();

return is a statement that is executed after Fl::run() returned and it
returns the return value of Fl::run() to the calling program - i.e. the
runtime system that will return it as the exit status to the shell or
whatever executed the program.

Which makes me ask another question: what exit status does a program
return when it is terminated with Cmd-Q in our current version? My demo
returns 0 under Linux in all cases. `echo $?' should give the answer.

> Under non MacOS, a normal GUI program should cleanly stop
> when all its windows are closed, because there's no way to
> interact with it through its GUI.

True. And it does this because Fl::run() returns and the program
terminates (returns from main()) after optional cleanup etc.. As I wrote
in my example, I consider terminating other threads and closing files to
be "normal" behavior after Fl::run(). I've seen examples that do this,
and even if not, we must assume that there are lots of programs out
there in the wild that do this.

> What is a normal MacOS program expected to do when all its
> windows close?
> Firefox, MSWord, Mail, Xcode, XQuartz, etc.. keep running
> because, through their system menu bar, it's possible to open a
> new window.

I consider this "special macOS" behavior. The majority of cross platform
FLTK programs won't do this. Unless they are especially coded for macOS.

Again, my point is that we need a consistent behavior across platforms
with *existing* code that is not broken by a system function that can be
easily implemented in a non-breaking way (let the program continue and
finish cleanly).

Please don't understand me wrong: I'm not discussing for myself. I'm
concerned about the implications of the current implementation for
compatible behavior of, as I said before, existing programs. We can't
assume that every programmer of every FLTK program changes the way he
programmed cleanup code and destructors (if that applies, see new demo
program) only because macOS introduces a new feature (Cmd-Q) and we
(FLTK) implement it in a non-compatible way!

> Therefore, cmd-Q does more than close all windows, it also terminates
> these programs.

It would be easy to add a new handler (as I suggested before) or use the
Cmd-Q global keyboard shortcut in macOS programs that want to support
this new feature of programs that run without open windows. It's a
design decision of FLTK that Fl::run() returns when all windows are
closed and (as documented in the beginners documentation) that the
program returns from main() when Fl::run() returns.

Everything else can be considered non-standard and programmers that want
to support this non-standard behavior can be required to do something
special in their programs. See below.

> What do we want a normal FLTK application to do on MacOS when all
> windows close?
> 1) exit after having run the cleanup code placed after the Fl::run()
> statement,
> if there's one?

Yes.

> or
>
> 2) allow the possibility to keep running (which requires to have put
> something
> in the application's system menu bar) ?

We can have both. If you want that behavior then it is non-standard and
you have to do "something special". I don't know what this would be, but
it can't be that complicated w/o sacrificing compatibility and
correctness of other existing programs. See an idea below.

> Choosing 1) means that applications behave identically across platforms,
> and requires to change in FLTK the present processing of cmd-Q, so Fl::run()
> returns after cmd-Q is processed.

Cmd-Q processing in FLTK is a "new" feature. I don't know when it was
introduced, I'm sure you know this better. Anyway, I believe it should
be changed to return NSTerminateCancel to the macOS Cmd-Q processing and
decide later (on program level) what to do. It's not FLTK's
responsibility to "kill" a program in an unconventional way, i.e. by not
returning from Fl::run() and (again, if that applies) prevent
destructors from being executed.

> Choosing 2) means that the standard behaviour of a Mac app is obtained,
> with the present FLTK code, putting
> #if __APPLE__
>   while (true) Fl::wait();
> #endif
> towards the end of main().

You can also do this with the modified Cmd-Q behavior.

> If we would change the processing of cmd-Q to stop it from terminating the
> app and instead terminate the event loop, then a MacOS app wanting to run
> with no open window would have to register a cmd-Q handler
> as a global shortcut and make it do what FLTK used to do.

That's exactly what I say. This is non-standard FLTK behavior and
(macOS) apps that want to support this must do "something" to make it
work. But not the majority of standard FLTK programs that terminate when
Fl::run() returns. It can be pretty easy to do, see below.

> With today's code, FLTK behaves unexpectedly only if its cleanup code
> does not find
> naturally its place in window callbacks. If it does, closing windows by
> cmd-Q
> calls the callbacks and the cleanup code runs.

Neither you nor me nor any other FLTK developer can decide what the
"natural" place of cleanup code in any application is or would be.
Particularly not if we consider all applications developed during the
last 20 years.

Again (sorry for the repetition): The main point is: we MUST NOT change
the behavior of existing programs that work as designed. We may add
features, but these added features may require the user (developer) to
do _something_.

> If an app is conceived so that ctrl-Q or cmd-Q stop it under
> all platforms,  it is necessary to define a global shortcut and to make it
> cleanly stop the app, most probably by stopping Fl:run() and running code
> placed later. This is done with only platform-independent code,
> and requires no change to FLTK, because FLTK's handling of cmd-Q does
> not run.
>
> Another possibility is that cmd-Q is handled by the app only on the
> MacOS platform.
> My reasoning is that, in that case, cmd-Q is expected to terminate the
> program for good
> when all windows replied to the FL_CLOSE event by closing themselves,
> because that's
> what most MacOS app do.
> A consequence would be that all cleanup code should be placed in window
> callbacks
> or in atexit() functions.
>
> I am ready to conclude this reasoning is too MacOS inspired if it
> creates difficulties for FLTK developers.

I think it's only the _default_ behavior of the current Cmd-Q processing
in the way that it terminates the app within Fl::run(). This is not
compatible with existing - unmodified - FLTK applications and must
strictly be avoided.

I'm sure we can find another way to make it easier (if necessary) for
macOS apps that want to continue to run after all windows are closed
(which is, as I said, more the exception from the rule, although it may
be typical for macOS applications). One possibility would be the handler
proposed by me, which would act like this:

macOS Cmd-Q processing calls
- FLTK Cmd-Q processing which
--- calls the handler if registered
--- sends FL_CLOSE events to all windows if requested/allowed
-- exits with NSTerminateCancel to continue the program

It would be the handler's responsibility to
- either terminate the program directly (if applicable)
- or set a flag that the program is to be terminated

If all windows have been closed and Fl::run() has returned the program
has to decide if it wants to continue without open windows. If the Cmd-Q
handler set the "terminate program" flag the program should exit cleanly
as it would on all other platforms.

If the "terminate program" flag was not set (i.e. the user closed all
windows) a macOS application would decide to wait by running Fl::wait()
in a loop as you explained elsewhere in this thread.

We could even add a new global FLTK variable and let the programmer
query or even reset it with accessor methods:

int Fl::terminate_program()

and

void Fl::terminate_program(0)

to reset the flag if desired. This way the registration of a handler
would not be necessary. FLTK's internal Cmd-Q processing could set the
variable so programs that call Fl::terminate_program() know that they
should terminate instead of running w/o windows.

This could even be compatible across platforms because this flag could
be initialized with 1 on non-macOS platforms, hence they would always
terminate when all windows are closed if they support running w/o
windows. Or something like that.

These changes would be minimal (register a handler or potentially only a
variable, ie. the above-mentioned "terminate program" flag or nothing at
all (if we use Fl::terminate_program()) and query this flag after
Fl::run() before deciding whether to terminate or to run continuously.

The main difference would be that all other (one-shot, aka standard
FLTK) applications would continue to work unmodified, whereas "special"
macOS programs that want to continue running w/o windows must be
changed. Since this is a new feature (especially) for macOS I believe
this would be the better decision.
hello_with_dtor.cxx

Manolo

unread,
Jan 29, 2018, 5:12:29 PM1/29/18
to fltk.coredev

====== output of new test program after cmd-Q ============

-Info- MyWindow constructor: global
-Info- Starting main.
-Info- MyButton constructor: local in main()
-Info- MyWindow: win_cb() - closing main window
*IMPORTANT* MyWindow destructor: global

*IMPORTANT* MyButton destructor: local in main()
test(50122,0x100472340) malloc: *** error for object 0x7ffeefbff6a0: pointer being freed was not allocated
==================================

that is, the program crashes....

Manolo

unread,
Jan 29, 2018, 5:17:45 PM1/29/18
to fltk.coredev
Answer to the question about the return status of a MacOS program
cleanly terminated with cmd-Q : 0

Ian MacArthur

unread,
Jan 29, 2018, 5:34:19 PM1/29/18
to fltkc...@googlegroups.com
That’s curious though, is it not?

That means the destructor was called (which we expected it would *not* be) but it appears the pointer was either corrupted or already free’d by the time that happened?



FWIW, I’m not too bothered what Cmd+Q returns, so long as we do not end up treating it as “close the current window” - it does have to mean “terminate this app”.

Perhaps we can do something to Fl::run() (or Fl::wait() ) so that once Cmd+Q has been received, it just returns straight away (so any subsequent attempt to show a window will return 0 immediately without checking or flushing the events?
Hmm, no, I don’t think that’ll work, actually...

We do need an ability to support something distinct from just the usual fltk “all the windows are closed” case for determining an app has exited, though, because on OSX, “all the windows are closed” needn’t mean the program has terminated, which is a slightly odd state compared to Win32 or X11 usage.
This would, I assume, use some OSX specific code the author would need to invoke if they wanted that particular behaviour on OSX...

FWIW, I suppose you *could* produce the same effect on X11 or Win32 by allowing the app to run in the background once all its windows had closed, assuming you had some way to signal it to wake up and show another window, but that usage would be “unusual” on those platforms, whereas it is commonplace on OSX (and there backgrounded apps still appear in the dock and show a menu bar when invoked, of course.)



Ian MacArthur

unread,
Jan 29, 2018, 5:58:26 PM1/29/18
to fltk.coredev
In case it helps, the backtrace from the crash looks like this...

(lldb) r
Process 6254 launched: '/Users/ian/src/junk/hello_with_dtor.app/Contents/MacOS/hello_with_dtor' (x86_64)
-Info- MyWindow constructor: global
-Info- Starting main.
-Info- MyButton constructor: local in main()
2018-01-29 22:45:05.218671+0000 hello_with_dtor[6254:589475] MessageTracer: load_domain_whitelist_search_tree:73: Search tree file's format version number (0) is not supported
2018-01-29 22:45:05.218702+0000 hello_with_dtor[6254:589475] MessageTracer: Falling back to default whitelist
-Info- MyWindow: win_cb() - closing main window
*IMPORTANT* MyWindow destructor: global
*IMPORTANT* MyButton destructor: local in main()
hello_with_dtor(6254,0x7fffa8157340) malloc: *** error for object 0x7ffeefbff8f0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Process 6254 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff6f57fe3e libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
-> 0x7fff6f57fe3e <+10>: jae 0x7fff6f57fe48 ; <+20>
0x7fff6f57fe40 <+12>: movq %rax, %rdi
0x7fff6f57fe43 <+15>: jmp 0x7fff6f5770b8 ; cerror_nocancel
0x7fff6f57fe48 <+20>: retq
Target 0: (hello_with_dtor) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
* frame #0: 0x00007fff6f57fe3e libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007fff6f6be150 libsystem_pthread.dylib`pthread_kill + 333
frame #2: 0x00007fff6f4dc312 libsystem_c.dylib`abort + 127
frame #3: 0x00007fff6f5d9866 libsystem_malloc.dylib`free + 521
frame #4: 0x0000000100001d1a hello_with_dtor`MyButton::~MyButton(this=0x00007ffeefbff8f0) at hello_with_dtor.cxx:31
frame #5: 0x0000000100012ca6 hello_with_dtor`Fl_Group::clear() + 256
frame #6: 0x0000000100012de8 hello_with_dtor`Fl_Group::~Fl_Group() + 24
frame #7: 0x0000000100001beb hello_with_dtor`MyWindow::~MyWindow(this=0x000000010004cec0) at hello_with_dtor.cxx:22
frame #8: 0x0000000100001695 hello_with_dtor`MyWindow::~MyWindow(this=0x000000010004cec0) at hello_with_dtor.cxx:20
frame #9: 0x00007fff6f4dd051 libsystem_c.dylib`__cxa_finalize_ranges + 351
frame #10: 0x00007fff6f4dd362 libsystem_c.dylib`exit + 55
frame #11: 0x00007fff4523431d AppKit`-[NSApplication terminate:] + 1930
frame #12: 0x00007fff457ea75a AppKit`-[NSApplication(NSResponder) sendAction:to:from:] + 312
frame #13: 0x00007fff4527f5f7 AppKit`-[NSMenuItem _corePerformAction] + 323
frame #14: 0x00007fff4527f37f AppKit`-[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] + 114
frame #15: 0x00007fff4527e1e3 AppKit`-[NSMenu performKeyEquivalent:] + 363
frame #16: 0x00007fff457e93c1 AppKit`routeKeyEquivalent + 884
frame #17: 0x00007fff457e68f5 AppKit`-[NSApplication(NSEvent) sendEvent:] + 1041
frame #18: 0x000000010000403a hello_with_dtor`fl_wait(double) + 185
frame #19: 0x0000000100004106 hello_with_dtor`fl_mac_flush_and_wait(double) + 182
frame #20: 0x000000010000e837 hello_with_dtor`Fl::run() + 19
frame #21: 0x0000000100001893 hello_with_dtor`main(argc=1, argv=0x00007ffeefbff9a0) at hello_with_dtor.cxx:68
frame #22: 0x00007fff6f430115 libdyld.dylib`start + 1
(lldb)


Which kinda looks like it does call the destructors here - it goes through NSApplication terminate: and all that, then starts the destructors, and chokes.

The value of this=0x00007ffeefbff8f0 for MyButton seems suspiciously large, if MyWindow itself is at this=0x000000010004cec0, and seems to work...?

OK, no, just checked it, and the value allocated for mb matches the failing value on Cmd+Q exit, so the memory has already been free’d by the time the destructor runs, in this case?





Ian MacArthur

unread,
Jan 29, 2018, 6:09:48 PM1/29/18
to fltk.coredev
Doh! I wasn’t looking...

Here’s the “normal" exit case:

ian$ ./hello_with_dtor
-Info- MyWindow constructor: global
-Info- Starting main.
-Info- MyButton constructor: local in main()
button: 0x7ffeea06ba30
-Info- MyWindow: win_cb() - closing main window
*IMPORTANT* After Fl::run(): closing files, terminating worker threads...
-Info- Returning from main.
*IMPORTANT* MyButton destructor: local in main()
*IMPORTANT* MyWindow destructor: global



Here’s the failing Cmd+Q exit case:

ian$ ./hello_with_dtor
-Info- MyWindow constructor: global
-Info- Starting main.
-Info- MyButton constructor: local in main()
button: 0x7ffee4e9ca30
-Info- MyWindow: win_cb() - closing main window
*IMPORTANT* MyWindow destructor: global
*IMPORTANT* MyButton destructor: local in main()
hello_with_dtor(6313,0x7fffa8157340) malloc: *** error for object 0x7ffee4e9ca30: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6


The crux is perhaps that the “normal" case tears down the widgets in a “sensible” order (child widgets first, then container) whereas the failing case is tearing them down container first, then children - and that doesn’t seem to be right here...



Albrecht Schlosser

unread,
Jan 29, 2018, 6:14:25 PM1/29/18
to fltkc...@googlegroups.com
Yep, that's correct.

I'm just describing it in another mail. Will post in a minute.

Albrecht Schlosser

unread,
Jan 29, 2018, 6:18:15 PM1/29/18
to fltkc...@googlegroups.com
On 29.01.2018 23:34 Ian MacArthur wrote:
>
>> On 29 Jan 2018, at 22:12, Manolo wrote:
>>
>>
>> ====== output of new test program after cmd-Q ============
>> -Info- MyWindow constructor: global
>> -Info- Starting main.
>> -Info- MyButton constructor: local in main()
>> -Info- MyWindow: win_cb() - closing main window
>> *IMPORTANT* MyWindow destructor: global
>> *IMPORTANT* MyButton destructor: local in main()
>> test(50122,0x100472340) malloc: *** error for object 0x7ffeefbff6a0: pointer being freed was not allocated
>> ==================================
>>
>> that is, the program crashes....
>
> That’s curious though, is it not?
>
> That means the destructor was called (which we expected it would *not* be) but it appears the pointer was either corrupted or already free’d by the time that happened?

Should I say: Sorry for the confusion ? See below why. ;-)

In fact the d'tor was obviously NOT called in the normal sense, i.e. not
because main() ended - otherwise it would have appeared before the
global MyWindow destructor as in my run. Then, when the global MyWindow
destructor is called, the (Fl_Group) MyWindow destroys all its children,
which is only one MyButton. But then the strange things happen. It
appears that <some> memory has already been freed or overwritten.

This makes me think that the stack are where MyButton was allocated
doesn't exist anymore or was overwritten, which seems to indicate that
main() was <somehow> terminated, supposedly by unwinding the stack,
likely with something similar to longjump.

So it's the test scenario that MyButton is a child of MyWindow that
leads to the (false) assumption that the destructor of MyButton was called.

It's only called because MyWindow's destructor destructs its children.
By (my) design of this demo program MyButton would be destroyed first
when main() goes out of scope. The d'tor of MyButton eventually calls
~Fl_Widget which removes itself from MyWindos (its parent group).

When MyWindow is destroyed there are no children (MyButton was already
deleted and removed), and everything works fine.

What do we conclude? Never ever must we use the current implementation
with NSTerminateNow. It does "funny" things with execution order because
it doesn't return from main() correctly.

It is even worse than I had imagined until now!!!

Manolo

unread,
Jan 30, 2018, 4:30:33 AM1/30/18
to fltk.coredev
When did the current processing of cmd-Q begin in FLTK?

At least since FLTK 1.3.0, released in July 2011.

Before then, FLTK was carbon-based, and it's more difficult,
using today's hardware, to make sure how cmd-Q stopped
an FLTK program.

This is informational. And an old implementation does not mean
it's not a wrong implementation.

Manolo

unread,
Jan 30, 2018, 5:16:11 AM1/30/18
to fltk.coredev
I could build FLTK 1.1.10 on my macintosh,
and replace the end of the hello program by
     window->show(argc, argv);
     Fl::run();
     fprintf(stderr,"after Fl::run()\n");
     return 0;
and see what happens.

Both cmd-Q and a window close by the mouse produce the same effect:
the code after Fl::run() executes, and main() returns.

Thus, the carbon-based version of FLTK implemented the equivalent
of what would be obtained by returning NSTerminateCancel instead of NSTerminateNow
after closing all windows.

Albrecht Schlosser

unread,
Jan 30, 2018, 1:09:08 PM1/30/18
to fltkc...@googlegroups.com
Yep, I agree. But whether it is "wrong" or not, I'm more and more coming
to the conclusion that we may need an option to enable the user to
switch behaviors on a program by program basis, depending on how a
particular program is coded. This option could also be a system wide
option if users or system managers want to set it once for all programs
on a particular system. I'm thinking of those options we introduced in
fluid to be set system wide, per user, or not at all.

In our case I can imagine to backport the change to 1.3.x, but maybe use
different defaults:

1.3.x: default = old behavior (NSTerminateNow)
1.4.0: default = new behavior (NSTerminateCancel)

The reasoning: the better option would be NSTerminateCancel, so this
should be the default in 1.4.0. macOS programs that rely on the 1.3.x
behavior can switch back to 1.3.x compatibility but should be changed to
use the new behavior soon (or whenever they need).

The situation in 1.3.x is different. There may be (macOS) programs that
rely on the "old" behavior so the default should be NSTerminateNow, but
we should recommend to switch to the new behavior (NSTerminateCancel).
Only macOS programs that use the "run w/o windows" mode are affected and
should be changed to use the new behavior.

If we can agree on this general guideline we can think about how to
implement this.

Comments and suggestion on how to proceed welcome...

Albrecht Schlosser

unread,
Jan 30, 2018, 1:11:36 PM1/30/18
to fltkc...@googlegroups.com
That's good news, although 1.1.10 should not be very important today.
But it's good to know that it is "OK" in 1.1.x.

Thanks for all your testing. I appreciate this.

Greg Ercolano

unread,
Jan 30, 2018, 1:42:47 PM1/30/18
to fltkc...@googlegroups.com
On 01/30/18 10:09, Albrecht Schlosser wrote:
> [..] But whether it is "wrong" or not, I'm more and more coming
> to the conclusion that we may need an option to enable the user to
> switch behaviors on a program by program basis, depending on how a
> particular program is coded. [..]

+1 for an option. Perhaps Fl::option() is a good fit for this.
http://www.fltk.org/doc-1.3/classFl.html#abc49722be45fb56cad89c7c067eda5e3

> This option could also be a system wide
> option if users or system managers want to set it once for all programs
> on a particular system. I'm thinking of those options we introduced in
> fluid to be set system wide, per user, or not at all.

IIRC, Bill never liked stuff like this, where the toolkit had its
own preferences. It made e.g. debugging problems hard, from both
the app programmer and user's point of view, because suspecting the
toolkit of a preference would be one of the last things either party
would think of when tracking down a problem, and the toolkit preference
would be long ago forgotten about when the problem comes up.

I have to agree, having been bitten by e.g. .Xdefaults and such
files/environment variables causing trouble that was hard to track
down in a networked environment, where variation in behavior across
machines turned out to be someone's ~/.Xdefaults file. Awfully hard
to track down unless one uses the debugger to trace the toolkit.

I'd offer: leave that problem of how to provide for a "system wide"
configurable option to the applications programmer to solve,
e.g. with Fl_Preferences, not the toolkit itself.

> In our case I can imagine to backport the change to 1.3.x, but maybe use
> different defaults:
>
> 1.3.x: default = old behavior (NSTerminateNow)
> 1.4.0: default = new behavior (NSTerminateCancel)

A backport to 1.3 would be nice, I'd be +1 if we're planning
to do an update to 1.3 anyway.

> The reasoning: the better option would be NSTerminateCancel, so this
> should be the default in 1.4.0. macOS programs that rely on the 1.3.x
> behavior can switch back to 1.3.x compatibility but should be changed to
> use the new behavior soon (or whenever they need).

This is so obscure, I'd think the default should be the new
behavior even in 1.3, but I'm not sure I'd care either way
for the 1.3 case.

For 1.4, the default should certainly be the new behavior,
it seems to me.


Ian MacArthur

unread,
Jan 30, 2018, 4:59:07 PM1/30/18
to fltk.coredev
Observations:

I’m not keen on having a global flag for this at all. Let us make it per application.

I’m OK with changing 1.4 to the NSTerminateCancel behaviour, but we do need to consider what happens in the case of a program that pops a series of windows in that case.

I don’t think updating 1.3 is all that critical - if this was really hitting a lot of folks, we’d have noticed before now.

I am not certain it needs to be configurable, if we put something in the docs. about this. I think we can produce either behaviour with the NSTerminateCancel return value, by judicious use of the Cmd+Q handler, or an atexit() method or etc.


Albrecht Schlosser

unread,
Jan 30, 2018, 7:39:43 PM1/30/18
to fltkc...@googlegroups.com
On 30.01.2018 19:42 Greg Ercolano wrote:
> On 01/30/18 10:09, Albrecht Schlosser wrote:
>> [..] But whether it is "wrong" or not, I'm more and more coming
>> to the conclusion that we may need an option to enable the user to
>> switch behaviors on a program by program basis, depending on how a
>> particular program is coded. [..]
>
> +1 for an option. Perhaps Fl::option() is a good fit for this.
> http://www.fltk.org/doc-1.3/classFl.html#abc49722be45fb56cad89c7c067eda5e3
>
>> This option could also be a system wide
>> option if users or system managers want to set it once for all programs
>> on a particular system. I'm thinking of those options we introduced in
>> fluid to be set system wide, per user, or not at all.

To be clear: What I wrote above was in my own words what you suggested
above: Fl::option(). Please note that (all ?) Fl::option() values are
backed by Fl_Preferences and can be changed by fluid (see link above):

"As of FLTK 1.3.0, options can be managed within fluid, using the menu
Edit/Global FLTK Settings."

In this menu a user or system manager with applicable access right can
set Fl::options per user or system wide as default values that are used
at program startup. Any program can set its own (local) values at runtime:

"Override an option while the application is running. This function does
not change any system or user settings."

http://www.fltk.org/doc-1.3/classFl.html#a9b040cad5d6fc04bb139877015091c16

That was my idea. If we use Fl::option() we get per user and per system
initial values (Fl_Preferences) w/o additional work (of course we must
add the new option to fluid so it can be managed).

> IIRC, Bill never liked stuff like this, where the toolkit had its
> own preferences. [...]
>
> I'd offer: leave that problem of how to provide for a "system wide"
> configurable option to the applications programmer to solve,
> e.g. with Fl_Preferences, not the toolkit itself.

We don't need to use Fl::option() with system wide and per user setting.
A simple static variable like Fl::Cmd_Q_mode would do the trick.

>> In our case I can imagine to backport the change to 1.3.x, but maybe use
>> different defaults:
>>
>> 1.3.x: default = old behavior (NSTerminateNow)
>> 1.4.0: default = new behavior (NSTerminateCancel)
>
> A backport to 1.3 would be nice, I'd be +1 if we're planning
> to do an update to 1.3 anyway.

Currently we have some updates "in the pipeline", i.e. in current svn.
These are some important bug fixes but also updates to support newer
macOS versions. We should probably release the current branch as 1.3.5
sooner or later.

Backporting to 1.3 however needs some care since we don't want to break
existing programs. For more info see below.

>> The reasoning: the better option would be NSTerminateCancel, so this
>> should be the default in 1.4.0. macOS programs that rely on the 1.3.x
>> behavior can switch back to 1.3.x compatibility but should be changed to
>> use the new behavior soon (or whenever they need).
>
> This is so obscure, I'd think the default should be the new
> behavior even in 1.3, but I'm not sure I'd care either way
> for the 1.3 case.

I believe that some macOS programs that use the "continuing w/o open
windows" feature would "break" if we changed the FLTK library to the new
behavior. Programs that would be recompiled or used with a new shared
FLTK library would get the new behavior w/o notice.

As far as I understood from Manolo's code fragments and explanations
these programs would not terminate when the user types Cmd-Q or uses the
"Quit" menu as they should and as they would do in 1.3.4.

Hence I believe that we should either NOT backport this new behavior to
1.3.5 OR have the "old" behavior as the default. Since I also think that
the old behavior is a bug I would really like to have the new behavior
backported, but only as an option so user programs that need the new
behavior set the option to use the new behavior.

Basically it's all about backwards compatibility in one minor version
(1.3.x), similar to ABI compatibility.

> For 1.4, the default should certainly be the new behavior,
> it seems to me.

Yes, I agree. We fix a bug with the introduction of 1.4.0 and provide an
option to switch to "old" behavior for programs that don't suffer from
the bug but would need some (likely more complicated) code changes to
work correctly with the new behavior.

I'm thinking of the scenario of the "typical user" who ports "his"
application to 1.4.x but who is not the original developer and doesn't
know how to rewrite the application to work correctly with the new
behavior. Advice like "add Fl_cmd_q_old(); at the beginning of main()"
would be easy to do for a user.

But maybe it's as simple as letting 1.3 as-is and switching to the new
behavior in 1.4.0 (as Ian suggested, with a per program option to switch
back to old behavior).

However, the OP in fltk.general was using 1.3.4-2 and his app was broken
(no crash, obviously, but destructors were not executed). Since 1.4.0 is
not yet released the only sensible solution would be to release 1.3.5
with an option to change the behavior. So I'm back to my latest proposal
(short form):

(1) add an option to set "Cmd-Q behavior" to old or new
(2) backport to 1.3 with default = old behavior (for backwards
compatibility!)
(3) change 1.4 to new behavior with option (1)

After reading the latest comments (by Greg and Ian) I think that the
option should be per program (only) so it could be a simple Fl:: static
member variable with an accessor function to read and write its value.

Note: such an option could be called in a version agnostic way by
setting the option value to 1 or 0 (true or false) in both 1.3.5 and
1.4.0 like so:

int main(...) {
Fl::Cmd_Q_mode(1); // 1 = new, 0 = old behavior
... application code ...
} // end of main

I'm open to suggestions for better function names. ;-)


Line 1505/1518 of Fl_cocoa.mm in FLTK 1.3.5/1.4.0, resp. would then read:

if (Fl::Cmd_Q_mode()) // new behavior
NSApplicationTerminateReply reply = NSTerminateCancel;
else // old behavior
NSApplicationTerminateReply reply = NSTerminateNow;

or equivalent ObjC code.

Manolo

unread,
Jan 31, 2018, 12:48:26 PM1/31/18
to fltk.coredev
It seems there's now a consensus among developers that the
cmd-Q procedure that terminates the app without following the
standard FLTK way to end an app by returning from Fl::run()
(or equivalent) goes against a strong design decision of FLTK.

Let's call this non-standard way to end an app "premature termination".

It's also clear that apps that don't need to run without
a mapped window would not be hurt by a change in the
library that prevents FLTK from performing "premature termination".

At this point, my proposal would be to respect this design decision
in all cases, even for a MacOS app that can run without window.
That is, FLTK would not provide an API to allow "premature termination",
which would never occur (except if the program calls exit()).

FLTK would always return NSTerminateCancel after cmd-Q is typed.
The program's main event loop could be written in 2 alternative ways:

1) the usual way (Fl::run(), or Fl::wait() in a loop) adequate
for all apps except those running without window on MacOS;

2) a new way that would support running without window
  Fl::system_driver()->run_windowless_if_supported()
and another new member function useful when access to each
event loop turn is needed:
  while ( Fl::system_driver()->wait_windowless_if_supported(5) ) continue;

Most user code would need no change.
User code for apps supporting windowless mode would have to make a one line
change: replace
  Fl::run();
by
  Fl::system_driver()->run_windowless_if_supported();
which is not more costly than calling a function to authorize
"premature termination".

The attached patch (relative to current FLTK 1.4) implements this proposal
and illustrates it with a modified hello.cxx that can be configured
in each of the alternative ways mentionned above.

1) if
#define RUN_WINDOWLESS 0 // what most FLTK apps would use
is active, the app follows the standard FLTK behavior that stops when all windows close.

2) if
#define RUN_WINDOWLESS 1   // what MacOS-friendly FLTK apps could use
is active, the app supports the windowless mode interruptible
by cmd-Q on MacOS, and behaves as above on other platforms.

All comments welcome.
"run_windowless_if_supported" is a bit heavy, ideas for improvement also welcome.

NSTerminateCancel.patch

Ian MacArthur

unread,
Jan 31, 2018, 5:47:19 PM1/31/18
to fltk.coredev


> On 31 Jan 2018, at 17:48, Manolo wrote:
>

> 1) the usual way (Fl::run(), or Fl::wait() in a loop) adequate
> for all apps except those running without window on MacOS;
>
> 2) a new way that would support running without window
> Fl::system_driver()->run_windowless_if_supported()
> and another new member function useful when access to each
> event loop turn is needed:
> while ( Fl::system_driver()->wait_windowless_if_supported(5) ) continue;
>


Do we need run_windowless_if_supported() et al though?

Someone who knew they wanted their app to run windowless could call Fl::wait() in a suitable loop once the last window was closed, and that would be broadly similar.

Indeed, that would then “work” on X11 and Win32 also... though how you would bring those to the foreground again I’m less certain. At least the OSX version might still have a dock icon and menu bar!

FWIW, I did basically that in the past for a Win32 application that places itself in the “tray” area when its window was hidden, and that pretty much worked.


Having CMD+Q return NSTerminateCancel in 1.4 looks basically benign for *most* use cases though, so I think we should probably do that?

Still the question of what an app that showed several windows sequentially would do in that case (I have used that idiom to create a “wizard” in the past so it is a real thing, albeit somewhat unusual...)





Albrecht Schlosser

unread,
Jan 31, 2018, 6:25:51 PM1/31/18
to fltkc...@googlegroups.com
On 31.01.2018 18:48 Manolo wrote:
> It seems there's now a consensus among developers that the
> cmd-Q procedure that terminates the app without following the
> standard FLTK way to end an app by returning from Fl::run()
> (or equivalent) goes against a strong design decision of FLTK.
>
> Let's call this non-standard way to end an app "premature termination".
>
> It's also clear that apps that don't need to run without
> a mapped window would not be hurt by a change in the
> library that prevents FLTK from performing "premature termination".
>
> At this point, my proposal would be to respect this design decision
> in *all* cases, even for a MacOS app that can run without window.
> That is, FLTK would not provide an API to allow "premature termination",
> which would never occur (except if the program calls exit()).
>
> FLTK would always return NSTerminateCancel after cmd-Q is typed.
> The program's main event loop could be written in 2 alternative ways:
>
> 1) the usual way (Fl::run(), or Fl::wait() in a loop) adequate
> for all apps except those running without window on MacOS;

Okay so far.

> 2) a new way that would support running without window
>   Fl::system_driver()->run_windowless_if_supported()
> and another new member function useful when access to each
> event loop turn is needed:
>   while ( Fl::system_driver()->wait_windowless_if_supported(5) ) continue;

This looks really ugly (and almost nobody could remember this ;-)).

Suggestion: let's put these new functions in the Fl:: class and do the
Fl::system_driver()->... thing internally (could be inline in the header
file), and let's shorten the names a little, so we have:

Fl::run() and Fl::run_windowless()

and

Fl::wait() and Fl::wait_windowless()

... or something similar. Doesn't this look better?

> Most user code would need no change.

That's good.

> User code for apps supporting windowless mode would have to make a one line
> change: replace
>   Fl::run();
> by
>   Fl::system_driver()->run_windowless_if_supported();
> which is not more costly than calling a function to authorize
> "premature termination".
>
> The attached patch (relative to current FLTK 1.4) implements this proposal
> and illustrates it with a modified hello.cxx that can be configured
> in each of the alternative ways mentionned above.

I can't try it but I believe it is good.

What I don't get is what Fl::wait_windowless() [or your previous
proposal] would exactly do. What is the argument? A float (seconds) like
in Fl::wait() ?

To recap and check if I understood it correctly, some statements that I
assume to be true. Please correct me if anything is wrong:

Fl::run() returns when all windows are closed as before on all platforms.

Fl::run_windowless() will run "forever" if the platform supports
"windowless" operation, i.e. only on macOS, otherwise it will be the
same as Fl::run().

On macOS (i.e. if the platform supports windowless operation)
Fl::run_windowless() will terminate if the user type Cmd-Q or used the
Quit menu *and* all windows are closed.

An internal flag (quit_event_loop) is used to know that the user typed
Cmd-Q (and all windows were closed) and the event loop should terminate.
In that case Fl::run_windowless() terminates.

I think this all sounds okay.

One point though (the case is more or less hypothetical, but it can
happen): If the user typed Cmd-Q and all windows were closed the flag
(static bool quit_event_loop) is set and the loop terminates. *If* the
program would re-enter (call) Fl::run_windowless(), wouldn't it terminate
(a) immediately (if no window was shown meanwhile)
or
(b) when all windows are closed regardless whether the user typed
Cmd-Q (again) because the static flag is not reset ? Is this intended?

What do we do with FLTK 1.3?

Manolo

unread,
Feb 1, 2018, 12:40:18 AM2/1/18
to fltk.coredev


On Wednesday, 31 January 2018 23:47:19 UTC+1, imacarthur wrote:

Do we need run_windowless_if_supported() et al though?

Someone who knew they wanted their app to run windowless could call Fl::wait() in a suitable loop once the last window was closed, and that would be broadly similar.
The main point is that if cmd-Q does not terminate the app but just terminates the event loop,
the code running after the loop must detect the difference between staying windowless and
not staying so because cmd-Q has been called before.
No combination of Fl::run(), Fl::wait() and Fl::wait(double) can do that.
 

Indeed, that would then “work” on X11 and Win32 also... though how you would bring those to the foreground again I’m less certain. At least the OSX version might still have a dock icon and menu bar!

FWIW, I did basically that in the past for a Win32 application that places itself in the “tray” area when its window was hidden, and that pretty much worked.


Having CMD+Q return NSTerminateCancel in 1.4 looks basically benign for *most* use cases though, so I think we should probably do that?

Still the question of what an app that showed several windows sequentially would do in that case (I have used that idiom to create a “wizard” in the past so it is a real thing, albeit somewhat unusual...)
Interesting. I had not realized this is a real thing.
I will investigate how to handle that.

Manolo

unread,
Feb 1, 2018, 1:23:12 AM2/1/18
to fltk.coredev


On Thursday, 1 February 2018 00:25:51 UTC+1, Albrecht Schlosser wrote:

> 2) a new way that would support running without window
>    Fl::system_driver()->run_windowless_if_supported()
> and another new member function useful when access to each
> event loop turn is needed:
>    while ( Fl::system_driver()->wait_windowless_if_supported(5) ) continue;

This looks really ugly (and almost nobody could remember this ;-)).
True!
 

Suggestion: let's put these new functions in the Fl:: class and do the
Fl::system_driver()->... thing internally (could be inline in the header
file), and let's shorten the names a little, so we have:

   Fl::run() and Fl::run_windowless()

and

   Fl::wait() and Fl::wait_windowless()

... or something similar. Doesn't this look better?
Much better indeed. Thanks.

Should we change that a little to
Fl::run_even_windowless()   or   Fl::run_also_windowless()
to make clear that the loop is also valid in the presence of active windows?


What I don't get is what Fl::wait_windowless() [or your previous
proposal] would exactly do. What is the argument? A float (seconds) like
in Fl::wait() ?
Yes, the argument is a maximum delay in seconds, just as in Fl::wait(double).
That's because some programs need access to each turn of the event loop
(e.g., test/keyboard), and possibly to cap the duration of the wait-for-event state.


 

To recap and check if I understood it correctly, some statements that I
assume to be true. Please correct me if anything is wrong:

Fl::run() returns when all windows are closed as before on all platforms.

Fl::run_windowless() will run "forever" if the platform supports
"windowless" operation, i.e. only on macOS, otherwise it will be the
same as Fl::run().

On macOS (i.e. if the platform supports windowless operation)
Fl::run_windowless() will terminate if the user type Cmd-Q or used the
Quit menu *and* all windows are closed.

An internal flag (quit_event_loop) is used to know that the user typed
Cmd-Q (and all windows were closed) and the event loop should terminate.
In that case Fl::run_windowless() terminates.

I think this all sounds okay.
Yes, all your statements above are correct.
 

One point though (the case is more or less hypothetical, but it can
happen): If the user typed Cmd-Q and all windows were closed the flag
(static bool quit_event_loop) is set and the loop terminates. *If* the
program would re-enter (call) Fl::run_windowless(), wouldn't it terminate
   (a) immediately (if no window was shown meanwhile)
or
   (b) when all windows are closed regardless whether the user typed
Cmd-Q (again) because the static flag is not reset ? Is this intended?
I'll have to experiment with this case. What is desirable is that the
program does (a) because cmd-Q means "Terminate the app if no user-
interaction prevents it", and not "Terminate the current event loop".
 

What do we do with FLTK 1.3?
At this point, my suggestion would be not to change the behaviour
of FLTK 1.3, and to seriously beef up the documentation of cmd-Q
in the "Operating system issues" section describing all possible ways
to terminate the app cleanly after cmd-Q.

An alternative would be:
1) change the FLTK code to prevent "premature termination".
That would go unnoticed by most apps, and would help those
that have post-loop cleanup actions. But, it would make windowless
apps nearly unstoppable on MacOS ...

and

2) provide a MacOS-specific access to an internal variable
to could be used to restore the present cmd-Q handling,
and that would no longer exist in 1.4
 

Manolo

unread,
Feb 1, 2018, 2:00:38 AM2/1/18
to fltk.coredev
 

What do we do with FLTK 1.3?
New attempt to reply.

We should aim at keeping apps functional when they were so with 1.3.4.

- Provide a MacOS-specific variable that can optionaly change the handling
of cmd-Q to prevent "premature termination". That variable would not survive
in 1.4

- Beef up the doc to explain issues and solutions.

Manolo

unread,
Feb 1, 2018, 11:42:26 AM2/1/18
to fltk.coredev
Attached NSTerminateCancel2.patch should fix what was discussed:
- use shorter names, e.g., Fl::run_also_windowless
- make sure a program showing several windows sequentially
does not create a new window after cmd-Q was successful before

NSTerminateCancel2.patch

Albrecht Schlosser

unread,
Feb 1, 2018, 2:45:58 PM2/1/18
to fltkc...@googlegroups.com
On 01.02.2018 17:42 Manolo wrote:
> Attached NSTerminateCancel2.patch should fix what was discussed:
> - use shorter names, e.g., Fl::run_also_windowless

Fine with me. But I'd like our native English speakers to comment on
these new function names and maybe suggest better one.

> - make sure a program showing several windows sequentially
> does not create a new window after cmd-Q was successful before

I don't like this implementation. I believe it would be better to
_allow_ creating other windows even after cmd-Q if the program really
wants to do it. The main reason is that a program can refuse to accept
cmd-Q by not closing all windows if the window callbacks don't call
window->hide(), hence it should also be allowed to ignore cmd-Q (i.e.
close windows, but don't terminate) and continue to show windows.

A macOS-friendly program that supports cmd-Q correctly would terminate
after the first successful cmd-Q. That's okay.

Another program that wants to open another window even after cmd-Q was
handled successfully should IMHO be _allowed_ to do this. It's not the
responsibility of the library to prevent this. Hence we shouldn't
disallow to create new windows in the first place and I would also reset
the internal static variable if Fl::run[_also_windowless]() is called
(again) after successful cmd-Q handling.

An example:

Fl_Window *window = new Fl_Window(...);
window->show(...);
Fl::run_also_windowless();

// if we get here cmd-Q was successfully handled, all windows were
// closed and a conformant program would exit.

// But THIS program is different ...

Fl_Window *other_window = new Fl_Window(...);

other_window->show(); // this would fail [1] [2]
// but this program _wants_ to do it anyway
// and FLTK must not forbid this!
Fl::run_also_windowless(); // this would return immediately [1]
// -- end of code --

[1] with the proposed patch

[2] see this part of the patch:
Fl_X* Fl_Cocoa_Window_Driver::makeWindow()
{
+ if (quit_event_loop) return NULL;

(end of example)

What I want to say: even if this example would not be strictly what
Apple (or Mac users) would expect it should still be possible to do it.
FLTK must not disallow something that doesn't conform to usual practice
or expectation - it should rather do what the programmer wanted (even if
it _seems_ to be "wrong").

In other words: if the programmer can refuse to accept cmd-Q handling
(by not closing all windows) it should also be allowed to open new
windows after cmd-Q has been handled successfully.

It would be even worse if the program executed creation of windows and
Fl::run_also_windowless(); in a loop. This would get us into an infinite
CPU loop instead of showing windows in a loop. And this behavior would
be different across platforms (only macOS would enter an infinite loop
w/o showing windows after the user typed cmd-Q once).

Ian MacArthur

unread,
Feb 1, 2018, 6:54:47 PM2/1/18
to fltk.coredev


> On 1 Feb 2018, at 19:45, Albrecht Schlosser wrote:
>
> On 01.02.2018 17:42 Manolo wrote:
>> Attached NSTerminateCancel2.patch should fix what was discussed:
>> - use shorter names, e.g., Fl::run_also_windowless
>
> Fine with me. But I'd like our native English speakers to comment on these new function names and maybe suggest better one.
>
>> - make sure a program showing several windows sequentially
>> does not create a new window after cmd-Q was successful before
>
> I don't like this implementation. I believe it would be better to _allow_ creating other windows even after cmd-Q if the program really wants to do it. The main reason is that a program can refuse to accept cmd-Q by not closing all windows if the window callbacks don't call window->hide(), hence it should also be allowed to ignore cmd-Q (i.e. close windows, but don't terminate) and continue to show windows.


Hmm, I don’t know about that.

In my head, hitting Cmd+Q on the Mac means “I really want you to stop, right now!”, so it’d be very surprising if the app then continued... At which point I’d be hitting “Force Quit” instead (which is a much harder key-combo, that I can’t remember in practice, so have to use the Apple menu to find it instead!)




Albrecht Schlosser

unread,
Feb 2, 2018, 8:06:53 AM2/2/18
to fltkc...@googlegroups.com
We agree that Cmd-Q means the program _should_ quit. That's okay.

But my point is not about "does the program behave as expected?" from a
user's point of view.

It's about the question "does the FLTK library" do what the programmer
requests - and if the programmer writes code that opens a window after
the user hit Cmd-Q then the FLTK library as such should respect this.

There are two possible scenarios:

(1) The user asks in fltk.general why a particular program does not
respect Cmd-Q and continues to show new windows. In this case we can
tell the users that they must ask the program's developer. We can
explain FLTK's features, but the program developer must fix it.

(2) The program developer asks in fltk.general why his program does not
open new windows. It fails silently. Maybe it runs in an infinite CPU
loop although he calls window->show() and Fl::run() or
Fl::run_also_windowless(), as I explained in my previous post. We can
tell the developer that it is not _allowed_ to continue the program
after the user hit Cmd-Q once we find out that this (user hit Cmd-Q) was
the case. Then the programmer may ask: "But my program needs to
continue. Is there a workaround?". We'd have to say: no, the FLTK
implementation doesn't allow this.

This is IMHO really bad. We can't imagine why it may necessary and
acceptable from the develper's view. It is also a question of
cross-platform compatibility. The given program would run perfectly on
platforms other then macOS - and even on macOS it would only fail if the
user hit Cmd-Q instead of closing the first window with the "red cross"
(close) button or escape.

This would be a bad behavior of the FLTK library. A library must not
enforce a particular behavior of a user action, even if we (or Apple)
consider this behavior "correct". Let's give the program developer the
choice to implement good or bad GUI behavior!

Another important point:

That said, Manolo's latest patch proposal has (IIRC) another important
issue. A programmer who uses the normal Fl::run() function can't
distinguish the cause of its return, i.e. if it returns because the user
hit Cmd-Q or because he closed all windows. Hence we need a way for the
programmer to decide this.

I suggest to make the internal variable

static bool quit_event_loop = false; // true means cmd-Q was called
and all windows closed

public so the programmer can see it and decide to quit the program even
after a normal Fl::run() call, just in case the program would otherwise
show another window (the wizard example mentioned by Ian).

It should also be an int for future extension, but currently I see only
0 or 1 as useful values.

This variable or its accessor method(s) should be available across
platforms (no need to use #if). It would never be 1 (true) on platforms
other than macOS and even on macOS only if Cmd-Q was hit and all windows
were closed after sending FL_CLOSE.

I propose Fl::quit() as its accessor method. It would be 1 (true) if the
program should quit after Fl::run() and 0 otherwise. "Normal" programs
would quit anyway, but "other" programs could use it like so:

int main() {
...
window->show();
Fl::run();
if (Fl::quit()) return 0;
other_window->show();
Fl::run();
...
}

I also propose to make this variable writable with

void Fl::quit(int val);

The value 0 would reset the quit flag. If a program did this it would
clearly state that it didn't respect/accept the Cmq-Q shortcut and
should be allowed to continue.

This would also "fix" the (IMHO) bad behavior of the latest patch
proposal, but I'd suggest to remove the offending code anyway.

The additional advantage of this new proposal would be that a programmer
could implement a menu and a callback similar to (macOS) Cmd-Q on other
platforms as well (or even on macOS) so the program (callback) gets
control when the user hits Cmd-Q (or Ctrl-Q or Ctrl-Alt-Q or anything
else), checks conditions (saves files etc.), asks the user to confirm,
closes windows in the order required by the program (maybe better than
sending FL_CLOSE to all windows in an arbitrary order), and finally
calls Fl::quit(1) to set the "quit flag" and return from Fl::run() in
the usual way because all windows were closed. This would enable the
program to do its cleanup stuff after return from Fl::run(), terminate
other threads etc., and exit cleanly.

Ian MacArthur

unread,
Feb 2, 2018, 1:28:07 PM2/2/18
to fltkc...@googlegroups.com

On 2 Feb 2018, at 13:06, Albrecht Schlosser wrote:


I suggest to make the internal variable

 static bool quit_event_loop = false; // true means cmd-Q was called and all windows closed

public so the programmer can see


Not public itself - but provide methods to access it, I guess?



it and decide to quit the program even after a normal Fl::run() call, just in case the program would otherwise show another window (the wizard example mentioned by Ian).

It should also be an int for future extension, but currently I see only 0 or 1 as useful values.

This variable or its accessor method(s) should be available across platforms (no need to use #if). It would never be 1 (true) on platforms other than macOS and even on macOS only if Cmd-Q was hit and all windows were closed after sending FL_CLOSE.

I propose Fl::quit() as its accessor method. It would be 1 (true) if the program should quit


Rather than explicitly 1, it would be “non-zero” in case we decide to encode different exit conditions at some future point?


after Fl::run() and 0 otherwise. "Normal" programs would quit anyway, but "other" programs could use it like so:

int main() {
 ...
 window->show();
 Fl::run();
 if (Fl::quit()) return 0;
 other_window->show();
 Fl::run();
 ...
}

I also propose to make this variable writable with

 void Fl::quit(int val);

The value 0 would reset the quit flag. If a program did this it would clearly state that it didn't respect/accept the Cmq-Q shortcut and should be allowed to continue.

And a programmer could choose to set it with this method too, e.g. in a window close callback  (I’m imagining this might be used to help indicate when to clean up other windows in a multi-window app for example... well, maybe not...)




This would also "fix" the (IMHO) bad behavior of the latest patch proposal, but I'd suggest to remove the offending code anyway.

The additional advantage of this new proposal would be that a programmer could implement a menu and a callback similar to (macOS) Cmd-Q on other platforms as well (or even on macOS) so the program (callback) gets control when the user hits Cmd-Q (or Ctrl-Q or Ctrl-Alt-Q or anything else), checks conditions (saves files etc.), asks the user to confirm, closes windows in the order required by the program (maybe better than sending FL_CLOSE to all windows in an arbitrary order), and finally calls Fl::quit(1) to set the "quit flag" and return from Fl::run() in the usual way because all windows were closed. This would enable the program to do its cleanup stuff after return from Fl::run(), terminate other threads etc., and exit cleanly.

OK, I am convinced!





Manolo

unread,
Feb 5, 2018, 12:05:16 PM2/5/18
to fltk.coredev
Attached NSTerminalCancel3.patch should fix the problems discussed till here.

The public API becomes
  int Fl::run_also_windowless(); // same as Fl::run() on non MacOS platforms
  int Fl::wait_also_windowless(double delay = 1e20); // default argument stands as 'infinite' delay
  int Fl::program_should_quit(); // returns 1 on MacOS after successful cmd-Q, 0 otherwise
  void Fl::program_should_quit(int should_i); // setter

Under FLTK 1.3.5, Fl::program_should_quit() makes no sense because the program
is terminated before the event loop stops. What could we do?
For sure, document extensively how cmd-Q is processed and the consequences of this processing.
We could also give access to a MacOS-specific variable that would force cmd-Q
to return NSTerminateCancel. But that variable would live only during 1.3.5


NSTerminateCancel3.patch

Albrecht Schlosser

unread,
Feb 5, 2018, 12:52:11 PM2/5/18
to fltkc...@googlegroups.com
On 05.02.2018 18:05 Manolo wrote:
> Attached NSTerminalCancel3.patch should fix the problems discussed till
> here.
>
> The public API becomes
>   int Fl::run_also_windowless(); // same as Fl::run() on non MacOS
> platforms
>   int Fl::wait_also_windowless(double delay = 1e20); // default
> argument stands as 'infinite' delay
>   int Fl::program_should_quit(); // returns 1 on MacOS after successful
> cmd-Q, 0 otherwise
>   void Fl::program_should_quit(int should_i); // setter

Thank you very much for the modified patch and the previous work. I
believe this patch is good to go, but I can't test it.

+1 for NSTerminateCancel3.patch

(excluding the mods to test/hello.cxx of course).

> Under FLTK 1.3.5, Fl::program_should_quit() makes no sense because the
> program
> is terminated before the event loop stops. What could we do?
> For sure, document extensively how cmd-Q is processed and the
> consequences of this processing.
> We could also give access to a MacOS-specific variable that would force
> cmd-Q
> to return NSTerminateCancel. But that variable would live only during 1.3.5

+1 for a variable that changes internal behavior to return NSTerminateCancel

I don't see a problem if this variable would be available only in 1.3.5
(and potential later 1.3.x versions). If we document this and users need
the behavior change it's easy to write conditional code like so:

#if FL_API_VERSION < 10400
// set the FLTK 1.3.5 variable here
#endif

If the program shall work with 1.3.4 or earlier the condition would have
to be changed, but it would obviously work as before (with all
consequences).

#if FL_API_VERSION >= 10305 && FL_API_VERSION < 10400
// set the FLTK 1.3.5 specific variable
#endif

Ian MacArthur

unread,
Feb 5, 2018, 5:24:16 PM2/5/18
to fltkc...@googlegroups.com


> On 5 Feb 2018, at 17:05, Manolo wrote:
>
> Attached NSTerminalCancel3.patch should fix the problems discussed till here.
>


I’m +1 on this change.

I have a few comments we can consider for the future though:

> The public API becomes
> int Fl::run_also_windowless(); // same as Fl::run() on non MacOS platforms
> int Fl::wait_also_windowless(double delay = 1e20); // default argument stands as 'infinite’ delay

These two are marked as being MacOS only, in effect (well, as being equivalent to the “regular” methods on non-MacOS hosts) but I wonder if that need necessarily be true?
There are ways this could be used on other hosts, such that the code would only exit if program_should_quit(); returns non-zero, whether any windows were shown or not, “exactly" as on MacOS.

The difference would (presumably) be in how program_should_quit would get set, since a non-MacOS app would presumably not still have a dock icon or menu-bar... However, it might still be set via a signal, or timer, or other message...

I could see that being used for app that get backgrounded, or for Win32 Tray apps, or etc...?


> int Fl::program_should_quit(); // returns 1 on MacOS after successful cmd-Q, 0 otherwise

...Returns non-zero if the app should terminate, e.g. on MacOS after a successful cmd-Q, or if the program explicitly sets it via program_should_quit(int should_i); returns 0 otherwise.

Again, I can see uses for this on all hosts, not just MacOS. It could be used, for example, as a way to signal child threads to expire in a multi-thread application (I assume *reading* this flag is contention free in the general case!)


> void Fl::program_should_quit(int should_i); // setter
>


> Under FLTK 1.3.5, Fl::program_should_quit() makes no sense because the program
> is terminated before the event loop stops. What could we do?
> For sure, document extensively how cmd-Q is processed and the consequences of this processing.
> We could also give access to a MacOS-specific variable that would force cmd-Q
> to return NSTerminateCancel. But that variable would live only during 1.3.5


I’m not sure what we should do abut 1.3.x. Should we “fix” this, or just leave it alone?
Are there ABI complications with fixing this, for example?


Manolo

unread,
Feb 6, 2018, 11:52:56 AM2/6/18
to fltk.coredev
Ian asks, very appropriately, if function
    int Fl::run_also_windowless();
could be made to run really windowless across platforms?
[with the patch, this function is a synonym of Fl::run(), except on MacOS]

This toy program can be used as a first test of this feature

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_ask.H>

void tmh(void *data) {
  if (!Fl::first_window()) Fl::program_should_quit(1);
  printf("turn program_should_quit ON\n");
}

// when the window closes, set a 5-second timeout that will turn Fl::program_should_quit ON
void close_cb(Fl_Widget *wid, void *data) {
  wid->top_window()->hide();
  Fl::add_timeout(5, tmh);
}

int main(int argc, char **argv) {
  Fl_Window *window = new Fl_Window(340,180);
  Fl_Box *box = new Fl_Box(20,40,300,100,"Hello, World!");
  box->box(FL_UP_BOX);
  box->labelfont(FL_BOLD+FL_ITALIC);
  box->labelsize(36);
  box->labeltype(FL_SHADOW_LABEL);
  window->end();
  window->callback(close_cb);
  window->show(argc, argv);
  // run the window normally until it's closed, and then survive without window for 5 seconds
  Fl::run_also_windowless();
  // make clear the intention to quit
  if (Fl::program_should_quit()) fl_alert("Good bye");
  printf("run cleanup code here\n");
  return 0;
}

Here are my observations on the 3 platforms.

1) With the patch, the toy program runs as desired on MacOS.

2) If Fl::run_also_windowless() is written for all platforms as it is
written for MacOS in the patch (of course Fl_XXX_Screen_Driver::wait(double)
remains platform-specific) then the toy program does not runs on X11 as expected,
because the it remains stuck waiting in poll/select even after the timeout
was triggered. It's possible to improve that a bit modifying Fl_X11_Screen_Driver::wait(double)
as follows: add this statement
     if (Fl::program_should_quit() && !Fl::first_window())  return 0;//experimental
before this line
    if (first_timeout && first_timeout->time < time_to_wait)

3) If the toy program is run under MSWindows, it runs as desired, but the program
consumes 100% CPU doing
   while ( Fl::wait_also_windowless() ) continue;
constantly during the 5 seconds after the window closes and before the timeout is triggered.

Thus, I would say the answer to Ian's question is "not quite".
1) we should find how to modify Fl_WinAPI_Screen_Driver::wait(double) so it does not return
immediately when there are no mapped window around.
2) we should find how to modify function Fl::program_should_quit(int should_i)
so it can interrupt an X11 program waiting in poll/select.


For now, we should keep with wait_also_windowless to mean 'wait_also_windowless_if_supported_by_platform'

Manolo

unread,
Feb 6, 2018, 3:18:40 PM2/6/18
to fltk.coredev
The result of this (long) discussion has been committed at r.12647 (and r.12648 for documentation)
to the FLTK 1.4 branch.
It's still necessary to commit some change for FLTK 1.3.5.

Solutions for how to run FLTK windowless acrosss platforms remain welcome.

Greg Ercolano

unread,
Apr 18, 2018, 6:32:35 AM4/18/18
to fltk.coredev
I just ran into this whole Command-Q thing myself while writing a vector font editor app
that checks if the user has unsaved data, as I'm concerned about any path that might cause
the user to loose what they've been doing by hitting an errant keystroke like Cmd-Q (very
close to Cmd-A, which is 'select all')

I was curious how the Mac's "Text Edit" program handled this, it's very interesting:

If you open text edit directly (no file opened), type some stuff, and then hit Command-Q,
text edit does simply quit without prompting for a "Do you want to save first?" dialog.

So what about the unsaved text? Surprise, it was saved for you. Where? I don't know,
as a filename was not specified. But if you open text edit again, the text is still there.

So it saves the unsaved text 'somewhere', probably as data similar to saving your last
window size, or some such.

All this is different from if you hit File -> Close or hit the red 'close' button for the window,
which does the usual 'ask the user if they want to save' thing.

This is interesting, and I guess provides a "graceful" way to e.g. log out or shutdown
without getting interrupted by lots of "Do you want to save first?" dialogs. It just auto-saves
"somewhere" allowing for a quick shutdown.

What happens if you /open a file/ and modify it, then hit Command-Q before saving?
It saves the file for you, overwriting it.

Ouch. Companies I've worked for in the past, if a machine needed to be shutdown/logged out
and the person who sat at that machine was out to lunch or had left for the day, policy
was agreed by all to NOT HIT SAVE for any open applications; if the users wanted to
have stuff saved, they would have saved it before walking away from their desk.

That said, I can see the more common "at home" case where a user has many open
applications, and decides to reboot, and assumes that everything will come back as it was
after the reboot.

So I /guess/ that's the rationale behind this currently very Mac OS-specific feature.

Reply all
Reply to author
Forward
0 new messages