GNOME Bugzilla – Bug 152035
Gtk+ Menus lose focus on first click in Win32
Last modified: 2004-12-22 21:47:04 UTC
1. run testgtk 2. run the menus test 3. click on test\nline2 menu item. The first time I click on the menu item it closes immediately. The second time it remains open. I've also noticed that in other cases the menu refuses to stay open unless I hold the mouse button down and drag it over the open menu window.
What's interesting is that in testgtk's menus test, clicking on the "foo" menu item does work as intended. One difference between "foo" and the "test\nline2" and "table" menu items is that "foo" is much shorter. The other ones would extend below the screen, and become scrollable. This might have something to do with this. Anyway, I don't think this can be classified as "high" priority and "major" severity. After all, it's just one extra click.
Forgot to ask, have you checked that this happens only on Win32?
It doesn't happen for me on X11.
It is only one click in the testgtk program, but when used with GtkEmbedTest in mozilla with the following patch applied http://bugzilla.mozilla.org/show_bug.cgi?id=256560 It is quite a few more clicks then just one or even two. That said, the gtkmozembed patch isn't necessarily the best way to get mozembed working in win32; however it is possible there are other cases with similar symptoms. Still, I'm curious what changed to cause this problem with the menus?
Has anyone else been able to confirm this bug in win32? I had been using the menu drop shadow patch from http://www.xfce.org/gtkmenu-shadow/ . I removed that patch and my menus are working fine now. So, I believe this bug can be closed unless anyone else has been experiencing similar problems in win32.
To me it looks like Tor confirmed this bug. And I can reproduce it as well using testgtk with the steps you described, with a very recent GTK+ 2.4, and I don't use that drop shadow patch. There have been changes to the menu code a few months ago, see for example bug #141169. Maybe that caused the problem, or uncovered a bug in the Win32 GDK backend.
The problem seems to be how the event timestamps are generated on win32, by _gdk_win32_get_next_tick() in gdkevents-win32.c. That function seems to make sure that each time stamp is unique (strictly increasing), but the problem is that when a large menu is realized a lot of window messages are received (WM_CREATE, WM_SIZE, WM_PAINT etc, for each item), which makes the timestamp for the button release event so large that it satisfies the (event->time - menu_shell->activate_time) > MENU_SHELL_TIMEOUT condition in gtk_menu_shell_button_release for the menu bar, and therefore the menu is immediately deactivated. This also explains why the problem shows up for the "test\nline2" and "table" menus but not for "foo" or "Help" - the first two menus contain a lot of items while the last two contain much fewer and therefore fewer messages are produced. Removing the "++" from _gdk_win32_get_next_tick solves the menu problem described in this bug and makes the tick numbers only nondecreasing but I don't know if it causes other problems. Looking at the CVS log for gdkevents-win32.c shows it was introduced in rev. 1.94: (_gdk_win32_get_next_tick): New function. Used to make sure timestamps of events are always increasing, both in events generated from the window procedure and in events gotten via PeekMessage(). Not sure whether this is actually useful, but it seemed as a good idea. Another observation is that _gdk_win32_get_next_tick might not be completely safe if someone manages to keep hir Windows system running for 50 days so that the tick count wraps around. Maybe that is not a very common problem :)
Fix committed to HEAD and gtk-2-4. Please open a separate bug report for the GetTickCount() wraparound issue if you feel strongly about it ;-) But X11 has the same issue, hasn't it? 2004-11-10 Tor Lillqvist <tml@iki.fi> * gdk/win32/gdkevents-win32.c (_gdk_win32_get_next_tick): Event timestamps don't have to be unique. As long as they are nondecreasing we should be fine. Solves problems with for instance long menus not staying up on first click. (#152035, Robert Ögren)
X11 doesn't have a wraparound problem. It has a well-defined "circular" time. It always interprets one half of the 32-bit timestamp space as future and the other half as past, the split being at the current server time.
Well, isn't "circular time" just an euphemism for (unsigned) wraparound? Exactly the same can be said of Windows timestamps (tick counts)? Or am I missing something? Does GTK take the "circular time" into account?
I'm sorry for being unclear, what I was thinking of was that _gdk_win32_get_next_tick will not let the time stamps wrap around and instead the returned values will get stuck at a high value. Some additional conditions would be needed in the if statement that decides whether to accept the new timestamp or use the current. Something like if (((suggested_tick <= cur_tick) && (cur_tick - suggested_tick < (1 << 31))) || (suggested_tick - cur_tick > (1 << 31))) return cur_tick; else return cur_tick = suggested_tick; This is completely untested of course ;) (I can open a new bug report for this, but there are much worse bugs that are more important to fix, such as #137551.)
Robert: Yes, and the GetTickCount() should perhaps be changed into GetMessageTime(). Not that it should make any huge difference. GetMessageTime() might be faster, if the timestamp of the last message that GetMessageTime() fetches is cached in userspace, while GetTimeCount() presumably dives into the kernel? (Or is using Unixish thinking like that wrong on Windows?)
Using GetMessageTime might yield more accurate timestamps on some events, but then the whole _gdk_win32_get_next_tick business should probably be scrapped and the users have to deal with timestamps in any order, if that is even a problem. I guess that reducing kernel-mode transitions is never wrong, but the problem is that in general you don't know how many such transitions a Windows API call will cause. The implementation of the API can of course vary between different Windows versions. So it is probably much better to spend development time on improving algorithms and such, and reducing the amount of API calls, preferably after some profiling to see which things are worth bothering with :) I conducted a quick study of GetMessageTime and GetTickCount, and on my Win XP box your guess seems to be wrong at least wrt kernel mode transitions ;) As you can see from the test below, GetTickCount just reads from some global memory (presumably mapped shared and readonly in all processes) and does a few calculations while GetMessageTime does a sysenter. I haven't actually timed the functions on the other hand, that is left as an exercise for the reader. The sysenter instruction is actually supposed to be pretty fast. See for example the following article for some gory details: http://www.codeguru.com/Cpp/W-P/system/devicedriverdevelopment/article.php/c8223/ or http://makeashorterlink.com/?T552523C9 Test: $ cat msgtime.c #include <windows.h> #include <stdio.h> int main(void) { int a = GetTickCount(); int b = GetMessageTime(); printf("%d %d\n", a, b); return 0; } $ gcc -o msgtime msgtime.c $ gdb ./msgtime (gdb) br main (gdb) r Breakpoint 1, 0x0040129d in main () (gdb) disp /i $eip nexti [some init crap omitted] 0x004012b5 in main () 1: x/i $eip 0x4012b5 <main+30>: call 0x401860 <GetTickCount@0> (gdb) stepi 0x00401860 in GetTickCount@0 () 1: x/i $eip 0x401860 <GetTickCount@0>: jmp *0x4040d0 (gdb) 0x7c8092ac in _libuser32_a_iname () 1: x/i $eip 0x7c8092ac <_libuser32_a>: mov $0x7ffe0000,%edx (gdb) 0x7c8092b1 in _libuser32_a_iname () 1: x/i $eip 0x7c8092b1 <_libuser32_a>: mov (%edx),%eax (gdb) 0x7c8092b3 in _libuser32_a_iname () 1: x/i $eip 0x7c8092b3 <_libuser32_a>: mull 0x4(%edx) (gdb) 0x7c8092b6 in _libuser32_a_iname () 1: x/i $eip 0x7c8092b6 <_libuser32_a>: shrd $0x18,%edx,%eax (gdb) 0x7c8092ba in _libuser32_a_iname () 1: x/i $eip 0x7c8092ba <_libuser32_a>: ret (gdb) 0x004012ba in main () 1: x/i $eip 0x4012ba <main+35>: mov %eax,0xfffffffc(%ebp) (gdb) 0x004012bd in main () 1: x/i $eip 0x4012bd <main+38>: call 0x4012f0 <GetMessageTime@0> (gdb) 0x004012f0 in GetMessageTime@0 () 1: x/i $eip 0x4012f0 <GetMessageTime@0>: jmp *0x404120 (gdb) 0x77d3c210 in _libuser32_a_iname () 1: x/i $eip 0x77d3c210 <_libuser32_a>: push $0xb (gdb) 0x77d3c212 in _libuser32_a_iname () 1: x/i $eip 0x77d3c212 <_libuser32_a>: call 0x77d3c617 (gdb) 0x77d3c617 in _libuser32_a_iname () 1: x/i $eip 0x77d3c617 <_libuser32_a>: mov $0x11b3,%eax (gdb) 0x77d3c61c in _libuser32_a_iname () 1: x/i $eip 0x77d3c61c <_libuser32_a>: mov $0x7ffe0300,%edx (gdb) 0x77d3c621 in _libuser32_a_iname () 1: x/i $eip 0x77d3c621 <_libuser32_a>: call *(%edx) (gdb) 0x7c90eb8b in _libuser32_a_iname () 1: x/i $eip 0x7c90eb8b <_libuser32_a>: mov %esp,%edx (gdb) 0x7c90eb8d in _libuser32_a_iname () 1: x/i $eip 0x7c90eb8d <_libuser32_a>: sysenter (gdb) 0x77d3c623 in _libuser32_a_iname () 1: x/i $eip 0x77d3c623 <_libuser32_a>: ret $0x4 (gdb) 0x77d3c217 in _libuser32_a_iname () 1: x/i $eip 0x77d3c217 <_libuser32_a>: ret (gdb) (etc) I don't know if we can get any more off-topic than this, and now I need some sleep ;)