Tk Source Code

View Ticket
Login
Ticket UUID: d4d89d137a37f38af0b040c56fafa45175f8c92b
Title: macOS/aqua: <Map> events are generated for windows that are already mapped, and wish crashes
Type: Bug Version: 9.0.2
Submitter: erikleunissen Created on: 2026-03-19 17:15:32
Subsystem: 69. Events Assigned To: marc_culler
Priority: 5 Medium Severity: Severe
Status: Closed Last Modified: 2026-05-19 13:38:53
Resolution: Fixed Closed By: erikleunissen
    Closed on: 2026-05-19 13:38:53
Description:
Observed on macOS/aqua Sequoia (using tclsh and wish 9.0.2):

    (all files used for the exercise are attached to this post)

$ cat exercise-map.tcl
package require Tk
bind all <Map> {puts "Serviced <Map> event for window %W ([incr i])"}
wm deiconify .
wm deiconify .
wm deiconify .
update
exit

$ tclsh exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)

And using wish a segfault occurs, sometimes not the first time, but at a
subsequent time (2nd, 3rd, ...) when executing the following command:

$ wish exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
/usr/local/bin/wish: line 2:  2523 Segmentation fault: 11  "$(dirname $0)/../../../Library/Frameworks/Tk.framework/Versions/9.0/Resources/Wish.app/Contents/MacOS/Wish" "$@"

Stack trace attached as file "stack_trace-map.txt".

And now similarly for unmap:

$ cat exercise-unmap.tcl
package require Tk
bind all <Unmap> {puts "Serviced <Unmap> event for window %W ([incr i])"}
wm withdraw .
wm withdraw .
wm withdraw .
update
exit

$ tclsh exercise-unmap.tcl
Serviced <Unmap> event for window . (1)
Serviced <Unmap> event for window . (2)
Serviced <Unmap> event for window . (3)

$ wish exercise-unmap.tcl
Serviced <Unmap> event for window . (1)
Serviced <Unmap> event for window . (2)
Serviced <Unmap> event for window . (3)

This case did not produce a segfault when repeated several times.

The expected result is that the binding scripts are executed only once
(as is the case on Linux/x11).
User Comments: erikleunissen added on 2026-05-19 13:38:53:
Yes Nicolas,

Please see [591829e948].

nab added on 2026-05-19 12:13:00:
Hi Erik,
is there's a place to discuss of  bug-591829e948-A branch?

++
nicolas

erikleunissen added on 2026-05-19 10:55:10:
Regarding:
 
>    I hope someone will eventually understand why it matters whether the
>    TK_MAPPED  flag is set before or after calling XMapWindow.

Might that be because there is a handler "windowActivation" registered for
NSWindowDidBecomeKeyNotification, NSWindowDidResignKeyNotification and
NSWindowWillCloseNotification. See:

	[https://core.tcl-lang.org/tk/file?ci=92224a0d1739f405&name=macosx%2FtkMacOSXWindowEvent.c&ln=369-371]

In your case, it's XMapWindow() generating a NSWindowDidBecomeKeyNotification,
which triggers the event handler. And there is your side effect!

marc_culler (claiming to be Marc Culler) added on 2026-04-01 16:05:13:
Thanks Nicolas.  And thanks for reporting your issue.

The CI runner seems to be happily displaying green checkmarks.  So I
am closing this ticket again.

I hope someone will eventually understand why it matters whether the TK_MAPPED
flag is set before or after calling XMapWindow.  But for now let's ignore our
ignorance about that and declare that if it works, it works.

nab added on 2026-04-01 04:30:02:
Hi Marc,
I've tested trunk with my app and I do not see the bad behavior anymore.

thanks,
++

marc_culler (claiming to be Marc Culler) added on 2026-04-01 01:55:38:
I restored the fix for this ticket, except without interchanging the
two lines that I included in my last post.

I also increased the RaiseDelay from 250 milliseconds to 300 in wm.test.

I was then able to tun wm.test 5 times with no failures.

Also, Nicolas' script worked correctly.

Maybe the CI runner will also pass the tests now.  Who knows?

marc_culler (claiming to be Marc Culler) added on 2026-04-01 00:04:09:
OK, I found the change which really did break Nicolas' script.
It is this:

     /*
      * Map the window and process a MapNotify event for it.
      */
 
-    XMapWindow(winPtr->display, winPtr->window);
     winPtr->flags |= TK_MAPPED;
+    XMapWindow(winPtr->display, winPtr->window);

Reverting that makes his script work as before.  So, somehow, we were
depending on a side effect of calling XMapWindow (not TkWmMapWindow) with
a window which is already mapped.

marc_culler (claiming to be Marc Culler) added on 2026-03-31 23:43:22:
I can confirm that removing the block:

   if (Tk_IsMapped(winPtr)) {
	return;
    }

makes Nicolas' script print out "Return reçu par: .dialog"

So I guess the next problem is to find the code that produces the side
effect that the dialog window becomes the Key Window.  But I think I would
also like to find the code which is calling TkWmMapWindow with a mapped
window and expecting the side effect that it becomes the Key Window.
That does not seem like an appropriate use of TkWmMapWindow to me.

marc_culler (claiming to be Marc Culler) added on 2026-03-31 22:49:48:
My first question is whether these two issues are related. Nicolas is
reporting that a window with grab is no longer receiving key events.
The test is (sometimes) reporting that a transient window is not
the top window.

The NSWindow which receives key events is the one designated as the Key
Window. I don't know whether the Key Window is always the top window, but
I don't think so.  However, we usually make a window become the Key Window
by calling makeKeyAndOrderFront, which would make it be the top window.
So it is at least very common for those two properties to coincide.  That
might be considered to be a relationship between them.

There aren't many changes in this commit.  And I can't imagine that either
of these issues could be affected by code blocks which begin:
if ([NSApp tkWillExit])

So I think that the culprit must be the block:
   if (Tk_IsMapped(winPtr)) {
	return;
    }
That would mean that we were depending on some side effect of calling
TkWmMapWindow with a window which is already mapped.  That does not seem
like something on which we should depend.  But it does seem likely to me
that we were doing that.

Of course, removing that block would very likely restore the issue which
Erik originally reported in this ticket.  There was no test for that issue,
and hence it could never create red checkmarks on the CI page.  But it
is a real bug nonetheless.

Maybe that block could be moved to be after the code which creates the
desirable side effects, wherever that is.

jan.nijtmans added on 2026-03-31 12:23:56:

Re-opening. I (unfortunately) temporary reverted this, so the cause of this regression can be investigated without disturbing other builds.


jan.nijtmans added on 2026-03-30 08:44:43:

@marc_culler: The Github CI build is red now for already 4 days. Can you please have a look?


nab added on 2026-03-28 09:20:20:
Hi,
for me the commit for this ticket have introduced a bug...
before this commit, a toplevel with local grab could intercept keybinding, now it doesn't

if you use this script:
package require Tk

# Bind Return sur toutes les fenêtres
bind all <Return> {puts "Return reçu par: %W"}

# Toplevel principale
toplevel .main
wm title .main "Fenêtre principale"

button .main.btn -text "Ouvrir avec grab" -command {
    toplevel .dialog
    wm title .dialog "Dialog (grab local)"
    label .dialog.lbl -text "Cette fenêtre a le grab local"
    button .dialog.close -text "Fermer" -command {destroy .dialog}
    pack .dialog.lbl .dialog.close -padx 20 -pady 10
    grab set .dialog
}

pack .main.btn -padx 40 -pady 20

# Cacher la fenêtre "." par défaut
wm withdraw .


before the commit, hitting Enter key produce:
Return reçu par: .main
they for the toplevel with grab :
Return reçu par: .dialog


now, it produces:
Return reçu par: .
they for the toplevel with grab :
Return reçu par: .



can you confirm ?
best regards,
nicolas

erikleunissen added on 2026-03-27 17:06:57:
At a surficial glance, I don't see how the fix for the current issue
affects the test's outcome, but this is more Marc's expertise.

Given your suspicion regarding the test, I think the test can be substantially
simplified while still adhering to its purpose. But I don't expect this to
remedy the intermittent test failures.

The appended diff:
- prevents the root window from interfering
- removes a redundant "update", which is overdone after having issued
  "raiseDelay"
- more succinctly construes what's needed to test that .t2 is above .t1
  (which appears to be the purpose of the last element of the expected result).


--- tests/wm.test
+++ tests/wm.test
@@ -2154,24 +2154,27 @@
     deleteWindows
 }

 test wm-transient-8.1 {transient to withdrawn window, Bug 1163496} -constraints {failsOnCILinux failsOnXQuartz} -setup {
     deleteWindows
+    wm withdraw .
+    after 10; update
     set result {}
 } -body {
     # Verifies that transients stay on top of their toplevels, even if they were
     # made transients when those toplevels were withdrawn.
-    toplevel .t1; wm withdraw  .t1;     update
+    toplevel .t1; wm withdraw  .t1; update
     toplevel .t2; wm transient .t2 .t1; update
     lappend result [winfo ismapped .t1] [winfo ismapped .t2]
     wm deiconify .t1; update
     lappend result [winfo ismapped .t1] [winfo ismapped .t2]
-    raise .t1; raiseDelay; update
-    lappend result [lsearch -all -inline -glob [wm stackorder .] ".t?"]
+    raise .t1; raiseDelay
+    lappend result [wm stackorder .t2 isabove .t1]
 } -cleanup {
+    wm deiconify .
     deleteWindows
-} -result {0 0 1 1 {.t1 .t2}}
+} -result {0 0 1 1 1}


 ### wm state ###
 test wm-state-1.1 {usage} -returnCodes error -body {
     wm state

jan.nijtmans added on 2026-03-27 11:41:35:

I'm seeing the following test failure, now and then (not always):

==== wm-transient-8.1 transient to withdrawn window, Bug 1163496 FAILED
==== Contents of test case:

# Verifies that transients stay on top of their toplevels, even if they were # made transients when those toplevels were withdrawn. toplevel .t1; wm withdraw .t1; update toplevel .t2; wm transient .t2 .t1; update lappend result [winfo ismapped .t1] [winfo ismapped .t2] wm deiconify .t1; update lappend result [winfo ismapped .t1] [winfo ismapped .t2] raise .t1; raiseDelay; update lappend result [lsearch -all -inline -glob [wm stackorder .] ".t?"]

---- Result was: 0 0 1 1 {.t2 .t1} ---- Result should have been (exact matching): 0 0 1 1 {.t1 .t2} ==== wm-transient-8.1 FAILED

Could this fix have introduced this? I'm suspecting a test-case problem here.


marc_culler (claiming to be Marc Culler) added on 2026-03-26 13:14:12:
Thanks Erik.  I merged the changes and I am closing the ticket now.

erikleunissen added on 2026-03-26 07:31:26:
I'm confirming that with commit [e03c75f51c] the repeated <Map> and <Unmap> events
are gone, and I was not able to induce a crash anymore (> 10 consecutive attempts).

Thanks Marc.

erikleunissen added on 2026-03-25 19:03:01:
Hi Marc,

I don't think I'll get around exercising the bugfix branch today. But I'm looking
forward to doing so tomorrow morning (I'm at UTC +1).

marc_culler (claiming to be Marc Culler) added on 2026-03-25 18:46:29:
Erik, I have created a bugfix branch bug-d4d89d137a which, I think,
fixes the crash with 9.0 and the issue with the redundant map and
unmap notifications.  This is a branch off of core-9-0-branch.

Note: For testing I added a line to exercise-map.tcl which first unmaps
the root window.  Otherwise there is no output, since the root is already
mapped when the first deiconify command runs.

marc_culler (claiming to be Marc Culler) added on 2026-03-25 16:29:08:
I can confirm that after updating to the tip of Tk 9.1 (something I did
not do when testing by remote login from my hotel) the crash no longer
occurs.

This is pretty unsatisfying.  I can only conclude that some update to 9.1
fixed this bug, but was not backported to 9.0.  What could it have been?

marc_culler (claiming to be Marc Culler) added on 2026-03-25 13:55:21:
I meant to say "does not occur when running it with tclsh9.1".

marc_culler (claiming to be Marc Culler) added on 2026-03-25 13:33:09:
It turns out that there is a simple explanation for why the crash occurs
when running exercise-map.tcl with wish9.1 but does not occur when running
it with wish9.1.

The crash occues in TkpExitProc.  But TkpExitProc is not called when using
tclsh.

erikleunissen added on 2026-03-23 13:07:02:
Fresh and remarkable observations:

A. I reduced the script exercise-map to what's needed to produce a crash:
   The update isn't necessary, and two invocations of "wm deiconify" suffice.
   The new script is attached as exerice-map-aqua-sgflt.tcl

B. I experienced contradictory circumstances leading to a crash or not.
   That led me to rebuild and re-install both Tcl/Tk9.0.3 and Tcl/Tk9.1.a1
   (the versions with symbols).

   Using these fresh installs:
   * I cannot induce a crash anymore using wish9.1 (from 9.1a1), only with
     wish9.0 (from 9.0.3.)
   * I learned that there is no relation between the number of invocations
     of "wm deiconify" and the number of invocations of the printf statement
     in canBecomeKeyWindow. (A brain-shortcut led me to assume so.)
     If canBecomeKeyWindow is invoked, it is always invoked as a multiple of
     three: 0, 3 and 6 times have been observed, also when using the reduced
     reproducible script, which has only two invocations of "wm deiconify".
     FWIW.

C. The stack traces in the crash reports, vary somewhat, but in the confusion
   that I mentioned at B., I lost track of which stack trace belongs to which
   invocation. I'm writing this just so that you don't miss this variability
   when you're reproducing the crash yourself.

erikleunissen added on 2026-03-23 12:40:05:
Regarding:

> I can reproduce the crash on the Intel Sequoia system

Except for this line, words almost fail to express my relief.

Regarding:

> I did not know that the crash only happens with wish.

That's mentioned in the original post, but I admit that it isn't conspicuous.

marc_culler (claiming to be Marc Culler) added on 2026-03-23 12:32:17:
Hi Erik, I did not know that the crash only happens with wish.  And, in
fact, I can reproduce the crash on the Intel Sequoia system.  That is
huge progress.  There must be a difference between how wish and tclsh handle
the shutdown process that is behind all of this.

erikleunissen added on 2026-03-23 07:27:14:
Hi Marc,

Please note that I can only induce a crash with (an installed) wish, not tclsh,
and that it sometimes happens only after several consecutive invocations
(I give up after 10).

marc_culler (claiming to be Marc Culler) added on 2026-03-22 21:54:23:
I just realized that, actually, I could test on my Intel Sequioa system by
remote login, since no interaction is needed.  (I don't have an Arm Sequoia
system.)

This is what I see on the Intel Sequoia system with tcl/tk 9.1a1:

% tclsh9.1 exercise-map.tcl 
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)

There is no crash.

marc_culler (claiming to be Marc Culler) added on 2026-03-22 21:47:12:
I can't test on Sequoia right now, because I am traveling and don't have
access to my Sequoia system.  I'll be able to test that when I get home.

erikleunissen added on 2026-03-22 18:35:54:
Just installed Tcl/Tk9.1a1 (no patches), and invoked:

$ wish9.1 exercise-map.tcl

which resulted in the same segfault and the stack trace as in the first report:
stack_trace-map.txt.

B.t.w. Marc, I'm wondering: did your attempts to reproduce the segfault include
an invocation on macOS Seqouia? Not that I have any idea how that can make a
difference for the present issue; I'm just trying to exclude for sure that you
can't reproduce because you're on Tahoe.

erikleunissen added on 2026-03-22 14:09:19:
Applied the patch, again with an extra printf statement, resulting in the following diff:

--- tkMacOSXWm.c.orig	2025-11-12 11:21:58
+++ tkMacOSXWm.c	2026-03-22 14:35:55
@@ -628,6 +628,11 @@
 
 - (BOOL) canBecomeKeyWindow
 {
+    if ([NSApp tkWillExit]) {
+	printf("BORK\n"); fflush(stdout);
+	return NO;
+    }
+
     TkWindow *winPtr = TkMacOSXGetTkWindow(self);
 
     if (!winPtr || !winPtr->wmInfoPtr) {
@@ -842,6 +847,10 @@
     NSWindow *ignore)
 {
     TkWindow *winPtr;
+    if ([NSApp tkWillExit]) {
+	printf("BORK2\n"); fflush(stdout);
+	return;
+    }
 
     /*
      * Avoid bug 5692042764: set tkEventTarget to NULL if there is no window to
@@ -1321,7 +1330,7 @@
 	}
 
 	/*
-	 * Find a new keyWindow.  It will be assinged as the new
+	 * Find a new keyWindow.  It will be assigned as the new
 	 * TkEventTarget when [NSApp WindowActivation] is called..
 	 */
 
--

Result of successive invocations:

$ wish exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
BORK2
$ wish exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
BORK2
$ wish exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
BORK2
BORK
BORK
BORK
/usr/local/bin/wish: line 2:  6612 Segmentation fault: 11  "$(dirname $0)/../../../Library/Frameworks/Tk.framework/Versions/9.0/Resources/Wish.app/Contents/MacOS/Wish" "$@"

--

The crash report now contains a stack trace that is different from the first
one. (Maybe because I'm now running Tk9.0.3. instead of Tk9.0.2?) I attached it
as stack_trace2-map.txt. Alas, with the previous exercise I didn't check
whether the stack trace was different from the one posted earlier because
no crash report popped up for several crashes in a row. I don't know why,
sorry.

marc_culler (claiming to be Marc Culler) added on 2026-03-22 13:23:32:
It looks like the tkWillExit flag is not being set when running your script.
I don't know why not, but that might explain why checking that flag does not
help.

marc_culler (claiming to be Marc Culler) added on 2026-03-22 13:02:19:
I thought the crash was happening in [w canBecomeKeyWindow] on line
873 of tkMacOSXWm.c.  But maybe the problem was not in the method itself,
but rather is caused by w being an invalid pointer.  I am guessing that w is
invalid because Apple has freed it already, as part of the shutdown process
for the NSApplication.  (And the timing of the various steps of that shutdown
process depends on the OS version.)

What does the crash report look like now?

I will attach a new patch that checks for the shutdown at an earlier time.

erikleunissen added on 2026-03-22 08:43:07:
I applied the patch with an extra printf statement as follows:

Index: macosx/tkMacOSXWm.c
==================================================================
--- macosx/tkMacOSXWm.c.orig
+++ macosx/tkMacOSXWm.c
@@ -628,6 +628,11 @@
     return frameSize;
 }

 - (BOOL) canBecomeKeyWindow
 {
+    if ([NSApp tkWillExit]) {
+	printf("BORK\n"); fflush(stdout);
+	return NO;
+    }
+
     TkWindow *winPtr = TkMacOSXGetTkWindow(self);

     if (!winPtr || !winPtr->wmInfoPtr) {
 	return NO;
     }

--

Here is the result of three consecutive invocations:

$ wish /Users/erik/tmp/bug/exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
$ wish /Users/erik/tmp/bug/exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
$ wish /Users/erik/tmp/bug/exercise-map.tcl
Serviced <Map> event for window . (1)
Serviced <Map> event for window . (2)
Serviced <Map> event for window . (3)
BORK
BORK
BORK
/usr/local/bin/wish: line 2: 55495 Segmentation fault: 11  "$(dirname $0)/../../../Library/Frameworks/Tk.framework/Versions/9.0/Resources/Wish.app/Contents/MacOS/Wish" "$@"

--

marc_culler (claiming to be Marc Culler) added on 2026-03-22 02:12:06:
It certainly provides some useful information.

I attached a patch file.  Do you still see crashes after applying that patch?

erikleunissen added on 2026-03-21 16:21:40:
I think I found out how to do it: just do "sudo make install" from the
"Development" build directory. I did so for both Tcl9.0.3 and Tk9.0.3
and I see a few more (but not many) symbols in the crash report.

I replaced the attached file stack_trace-map.txt with the new copy.

Does this do the job?

erikleunissen added on 2026-03-21 15:48:35:
Regarding:

>    1) Some of what was said about TK_MAPPED does not make sense, because
>    that  flag is only meaningful to TkMapWindow and TkUnmapWindow. It is
>    not visible  to X11. It probably does not make sense for XMapWindow to
>    do anything  with that flag, since the X11 version certainly can not.

I'm unsure. See how the flag TK_MAPPED is set inside XMapWindow() on win32, here:

    [https://core.tcl-lang.org/tk/file?ci=dcb07432f9b6303f&name=win%2FtkWinWindow.c&proof=305769761&ln=368]


>    2) The crash report suggests that the crash happens during shutdown
>    and  involves Apple code. There is a flag [NSApp tkWillExit] which
>    gets set  when the NSApp begins its shutdown process. It might help to
>    make  XMapWindow and XUnmapWindow check that flag and return
>    immediately if  it is set. But it is not clear which Tk function is
>    actually involved  in the crash. Could you please generate a crash
>    report from a Tk built  with symbols, so we could see which Tk
>    function makes the invalid memory  access?

In understand your request. However, I've got a problem here. I can only
induce the crash with an installed wish (i.e. invoking wish after I did
"sudo make install"). And the program installed is always the copy built without
symbols. I cannot induce the crash when invoking wish from any of the build
directories ("Deployment" or "Development"), either by doing
"./wish exercise-map.tcl" or "make shell SCRIPT=exercise-map.tcl".

I'm willing to install a copy that was built with symbols but I'm too
unfamiliar with macOS to be able to do that. Someone would need to spell
this out for me.

marc_culler (claiming to be Marc Culler) added on 2026-03-21 13:06:51:
Two comments:

1) Some of what was said about TK_MAPPED does not make sense, because that
flag is only meaningful to TkMapWindow and TkUnmapWindow.  It is not visible
to X11.  It probably does not make sense for XMapWindow to do anything
with that flag, since the X11 version certainly can not.

2) The crash report suggests that the crash happens during shutdown and
involves Apple code. There is a flag [NSApp tkWillExit] which gets set
when the NSApp begins its shutdown process.  It might help to make
XMapWindow and XUnmapWindow check that flag and return immediately if
it is set.  But it is not clear which Tk function is actually involved
in the crash.  Could you please generate a crash report from a Tk built
with symbols, so we could see which Tk function makes the invalid memory
access?

erikleunissen added on 2026-03-21 09:42:46:
I need to withdraw the previous post. Sorry!

The segfault is there again with a fresh build/installation of wish 9.0.3.

erikleunissen added on 2026-03-21 09:34:32:
Regarding the separate issue of the segfault that I reported:

With a refreshed build of Tcl/Tk 9.0.2 the segfault could be reproduced again.
With a fresh build of Tcl/Tk 9.0.3 the segfault was gone.

So, I think we can safely forget about the segfault in this ticket.

erikleunissen added on 2026-03-21 08:58:49:
Another interesting fact:

Out of curiosity, I ran the script exercise-map.tcl with my bug-fix branch for
ticket [591829e948], at commit [751252ea8d]. This ticket intends to fix another bug, but operates in the same area of the macOS/aqua codebase.

And surprise: the issue for the present ticket is gone there. I guess that that
must be another positive effect of the removal of the embedded event loop here:

    [https://core.tcl-lang.org/tk/file?ci=dcb07432f9b6303f&name=macosx%2FtkMacOSXInit.c&ln=656]

erikleunissen added on 2026-03-21 08:32:17:
W.r.t.

> So: because win32 also uses its own platform-specific (un)mapping functions,
> it might be instructive to have a look at the implementation there.

Uhmm that's confusing. I meant:

Because win32 (like macOS/aqua) also uses Tk-implemented emulations of (un)mapping
functions such as XMapWindow, it might be instructive to have a look at the
implementation there.

erikleunissen added on 2026-03-21 08:22:32:
By the way, win32 is also impervious to the problem: 

> tclsh90 exercise-map.tcl
Serviced <Map> event for window . (1)

And after adding an extra update into the file exercise-unmap.tcl, it appears to
be unaffected for that event as well:

> tclsh exercise-unmap.tcl
Serviced <Unmap> event for window . (1)

   (The fact that the extra update is necessary in exercise-unmap.tcl may indicate
    another issue, but that's besides the point for the present ticket.)

So: because win32 also uses its own platform-specific (un)mapping functions,
it might be instructive to have a look at the implementation there.

I replaced the attached file exercise-unmap.tcl with the copy that accommodates win32.

erikleunissen added on 2026-03-21 07:52:33:
> Why doesn't that cause redundant MapNotify events in the case of X11?

Please see:

    [https://core.tcl-lang.org/tk/file?ci=dcb07432f9b6303f&name=generic%2FtkWindow.c&ln=1789-1791]

chw added on 2026-03-21 06:20:18:
> Why doesn't that cause redundant MapNotify events in the case of X11?

Maybe due to "." being a toplevel with special relationship to the
enclosing environment, i.e. a wrapper window and finally the
window manager. So many more things than the XMapWindow() function
are involved in this case.

marc_culler (claiming to be Marc Culler) added on 2026-03-21 02:32:11:
In the case of X11 we have no control over the XMapWindow function,
since we have to use the one provided by X11.  Moreover, according to
the man page to which Erik provided a link:

*This function has no effect if the window is already mapped.*

So that would seem to be a pretty strong argument in favor of making the
non-X11 XMapWindow functions do nothing if the window is already mapped.

The X11 man page is too brief to discuss the other two operations, but I
presume that the X11 XMapWindow also performs them.  However, that makes
it very mysterious that Tk is currently setting the TK_MAPPED flag and
generating MapNotify events.  Why doesn't that cause redundant
MapNotify events in the case of X11?

erikleunissen added on 2026-03-20 19:39:56:
See also:

    [https://x.org/releases/X11R7.7/doc/man/man3/XMapWindow.3.xhtml]

erikleunissen added on 2026-03-20 19:36:02:
Re:

> These two operations need to be moved into XMapWindow, and all other callers
> of XMapWindow need to be checked as well.

Or those operations need to be performed by the caller, depending on
a return value.

erikleunissen added on 2026-03-20 19:32:34:
@Marc,

On second thoughts, regarding:

> I see from the generic code that it very common to call XMapWindow
> with a  mapped window as the argument. So I guess XMapWindow is
> responsible for  checking if the state has changed (not TkMapWindow).
>
> Would this be fixed by making xMapWindow return immediately if the
> window  is already mapped?

I had a closer look at the code for TkWmMapWindow and XMapWindow. And also
since you already checked generic code, I can't imagine how this can be done in
a better way than you suggest. So, I'm all for just trying this out.

But I also saw two related issues, that need to be taken care of then.
In your suggestion, XMapWindow may return with accomplishing anything.
Therefore, it makes no sense to:
- set the TK_MAPPED flag before the call to XMapWindow (as now is done in TkWmMapWindow)
- generate the MapNotify event after the call to XMapWindow (likewise).

These two operations need to be moved into XMapWindow, and all other callers
of XMapWindow need to be checked as well.
--

nab added on 2026-03-20 18:07:56:
Hi,
I do use mac ports with my app and I've a mecanism to check when <Map> is triggered.
I do not have <Unmap> event that trigger anything but the only thing that could be related is when I destroy toplevels...
maybe Csaba also knows things about map/unmap

after many years... my code for <Map> is mostly wrapped in an after 0 [...] thing or in an OO method (in order for it to work).

if you imagine another approach i can test.. (if it's relevant for you...)

best rehgards,
nicolas

erikleunissen added on 2026-03-20 17:42:03:
Regarding:

> I see from the generic code that it very common to call XMapWindow
> with a  mapped window as the argument. So I guess XMapWindow is
> responsible for  checking if the state has changed (not TkMapWindow).
>
> Would this be fixed by making xMapWindow return immediately if the
> window  is already mapped?

It seems so doesn't it? My first inclination is to look and check how
this is handled in the Tk code for the other platforms.

> That would be a simple change. I have to
> wonder, though,  why that was not done when XMapWindow was written
> over 20 years ago for the  initial Aqua port.

Yes, that's very remarkable. When writing code that depends on detecting
<Map> and <Unmap> events, this issue must have caused trouble. Apparently,
nobody did that, or nobody cared to complain about it.

> Is there some subtle issue with that?

I wouldn't know Marc. But I understand that one does get suspicious indeed.
I think it's wise to ask this to someone with a longer experience with
the codebase than mine.

Also note that I happened to detect the issue for <Map> and <Unmap> events.
But if this issue has been sitting in the code base since the beginning of
the aqua port, then it's only natural to ask oneself what more may there
be lurking in the shadows.

Maybe, the repeated generation of window events is more general than just
those two events. Or maybe it applies to other windows than toplevels too.
I didn't check that. My initial report covered only what I observed in the
situation where I was in at the time. If you don't know any more, then I
think it's wise to broaden the view.

marc_culler (claiming to be Marc Culler) added on 2026-03-20 15:01:10:
I see from the generic code that it very common to call XMapWindow with a
mapped window as the argument. So I guess XMapWindow is responsible for
checking if the state has changed (not TkMapWindow).

Would this be fixed by making xMapWindow return immediately if the window
is already mapped?  That would be a simple change. I have to wonder, though,
why that was not done when XMapWindow was written over 20 years ago for the
initial Aqua port. Is there some subtle issue with that?

erikleunissen added on 2026-03-20 11:47:42:
Regarding your remaining questions, a general remark first off:

It strikes me that you seem not to recognize the notion that a <Map> event
(as most other window events, in any case all window state events) signals
a change in window state. Do you perhaps disagree with that notion?

If that notion isn't obvious to you, then that's an issue to be solved first.
Please see the document for the X11 standard (which Tk emulates for win32
and aqua):

    [https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#MapNotify_Events]

It says:

    "The X server generates this event type whenever a client application changes
     the window's state from unmapped to mapped by calling XMapWindow, XMapRaised,
     XMapSubwindows, XReparentWindow, or as a result of save-set processing."

Note the word "changes"!

And note the use of that word in general for many other window events, but
especially for window state events. See:

        [https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#Window_State_Change_Events]


Concluding: <Map> events signal the change from the unmapped state to the mapped state.
This basic notion underlies the answer to most of your questions below:

> Why would you expect the binding scripts to only be executed once,
> when the  deiconify command is called three times?

Because the 2nd an 3rd invocations of "wm deiconify" do not bring about
a change in window state a <Map> event ought not be generated.

> Possibly the question is whether to generate a <Map> event in the case
> where the window is already mapped.

That is the question indeed, of course. And I trust that deductive reasoning
leads you to the answer.

> But that is not related to how event  bindings are handled.
> It has to do with how Xevents are generated.

I'm sorry, I don't understand (the relevance of) this remark for the present issue.

> Is  there a specification in the manual about whether <Map>
> should be generated  if a window in normal state is deiconified?

Please see my first remark above.

erikleunissen added on 2026-03-20 11:43:32:
Hi Marc,

Regarding the segfault:

> I ran those scripts 25 times each on my M3 macbook air with macOS
> 26.3.1.  I did not see any segfaults.

OK. That's a pity.

I checked again (after reboot), and I reproduced the crash on the third
invocation. I'm using wish 9.0.2 on a M2 Mac Mini with macOS Sequoia.

marc_culler (claiming to be Marc Culler) added on 2026-03-20 01:36:52:
I ran those scripts 25 times each on my M3 macbook air with macOS 26.3.1.
I did not see any segfaults.

Why would you expect the binding scripts to only be executed once, when the
deiconify command is called three times?  In any case, calling binding
scripts is the job of the generic code.  So I can't explain why there
would be a difference between platforms in how they are handled.

Possibly the question is whether to generate a <Map> event in the case
where the window is already mapped.  But that is not related to how event
bindings are handled.  It has to do with how Xevents are generated.  Is
there a specification in the manual about whether <Map> should be generated
if a window in normal state is deiconified?

Attachments: