Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Michel Donais

Good day,

 

I am having a field trip working on GTK+ inside our app here. Mostly, I am currently trying to integrate a GTK container to an existing foreign (Win32) window. I was able to import the parent, import the events, get things being sent out to me. However, I am having issues with parent (foreign, system) window being destroyed and propagating the event to my container.

 

What seems to happen is the WM_NCDESTROY event is being sent out to GDK because its hierarchy is getting destroyed by Win32, and then, it seems GDK does the good thing destroying all its structures and pushes the destruction to all its known children, but it doesn’t propagate the good news to the Widget. It doesn’t seem to send events internally nor externally, and I don’t seem to get any event on the GTK side to tell I don’t have a surface, or got destroyed or anything.

 

Anyone knows a quick and efficient way to propagate this to an user function so I can call destroy on the GTK Widget itself, or having GDK/GTK binding to tell when the GDKWindow gets destroyed, its GTKWidget should get destroyed too.

 

Thanks

Michel Donais


_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Gtk+ - General mailing list
On 18.09.2018 18:59, Michel Donais wrote:
> What seems to happen is the WM_NCDESTROY event is being sent out to GDK
> because its hierarchy is getting destroyed by Win32, and then, it seems GDK
> does the good thing destroying all its structures and pushes the destruction
> to all its known children, but it doesn’t propagate the good news to the
> Widget. It doesn’t seem to send events internally nor externally, and I
> don’t seem to get any event on the GTK side to tell I don’t have a surface,
> or got destroyed or anything.

Is far as i can see from the source code, GDK W32 backend emits GDK_DESTROY
event. On the GTK side it is, i think, supposed to be handled in
gtk_widget_event_internal(), which should emit a DESTROY_EVENT signal. If that
doesn't happen, then the best way to debug it is to get GTK with debug info,
run under gdb, and set up breapoints in all important places to see how
everything happens.

I assume you're using GTK3, by the way. Haven't looked at GTK2 yet.

Do note that foreign windows are highly unlikely to be supported in GTK4 (just
FYI).


_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list

signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Michel Donais
You are right, this is on GTK3. Hopefully, once GTK4 becomes predominant, it might not be useful to have foreign windows. But TBD!

The problem is as follows:

>      if ((window != NULL) && (msg->hwnd != GetDesktopWindow ()))
> gdk_window_destroy_notify (window);

This line actually destroys the window, so GDK_WINDOW_DESTROYED becomes true at that point. This is due to:

> static void gdk_win32_window_destroy_notify (GdkWindow *window)

where

> if (!GDK_WINDOW_DESTROYED (window)),

it

>       _gdk_window_destroy (window, TRUE);

So the line after, once the window is actually destroyed, it breaks

>      if (window == NULL || GDK_WINDOW_DESTROYED (window))
> break;

and never enters the following lines.

>      event = gdk_event_new (GDK_DESTROY);
>      event->any.window = window;
>
>      _gdk_win32_append_event (event);



On Tue, Sep 18, 2018 at 12:27 PM LRN via gtk-list <[hidden email]> wrote:
On 18.09.2018 18:59, Michel Donais wrote:
> What seems to happen is the WM_NCDESTROY event is being sent out to GDK
> because its hierarchy is getting destroyed by Win32, and then, it seems GDK
> does the good thing destroying all its structures and pushes the destruction
> to all its known children, but it doesn’t propagate the good news to the
> Widget. It doesn’t seem to send events internally nor externally, and I
> don’t seem to get any event on the GTK side to tell I don’t have a surface,
> or got destroyed or anything.

Is far as i can see from the source code, GDK W32 backend emits GDK_DESTROY
event. On the GTK side it is, i think, supposed to be handled in
gtk_widget_event_internal(), which should emit a DESTROY_EVENT signal. If that
doesn't happen, then the best way to debug it is to get GTK with debug info,
run under gdb, and set up breapoints in all important places to see how
everything happens.

I assume you're using GTK3, by the way. Haven't looked at GTK2 yet.

Do note that foreign windows are highly unlikely to be supported in GTK4 (just
FYI).

_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list


--
  
Michel Donais
Développeur logiciel senior

_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Gtk+ - General mailing list
On 18.09.2018 19:38, Michel Donais wrote:

> You are right, this is on GTK3. Hopefully, once GTK4 becomes predominant,
> it might not be useful to have foreign windows. But TBD!
>
> The problem is as follows:
>
>>      if ((window != NULL) && (msg->hwnd != GetDesktopWindow ()))
>> gdk_window_destroy_notify (window);
>
> This line actually destroys the window, so GDK_WINDOW_DESTROYED becomes
> true at that point. This is due to:
>
>> static void gdk_win32_window_destroy_notify (GdkWindow *window)
>
> where
>
>> if (!GDK_WINDOW_DESTROYED (window)),
>
> it
>
>>       _gdk_window_destroy (window, TRUE);
>
> So the line after, once the window is actually destroyed, it breaks
>
>>      if (window == NULL || GDK_WINDOW_DESTROYED (window))
>> break;
>
> and never enters the following lines.
>
>>      event = gdk_event_new (GDK_DESTROY);
>>      event->any.window = window;
>>
>>      _gdk_win32_append_event (event);
>
I see. I've looked at X11 backend, and it does things a bit differently - it
always queues a GDK_DESTROY event (due to how event processing is being done in
X11 - any notification results in an event, it seems), while also maybe calling
destory_notify() in the process.

If you rewrite WM_NCDESTROY handling to:

if ((pointer_grab != NULL && pointer_grab -> window == window) ||
    (keyboard_grab && keyboard_grab -> window == window))
  {
    GdkDevice *device = gdk_device_manager_get_client_pointer (device_manager);
    gdk_device_ungrab (device, msg -> time);
  }

if (window != NULL)
  {
    event = gdk_event_new (GDK_DESTROY);
    event->any.window = window;

    _gdk_win32_append_event (event);
  }

if ((window != NULL) && (msg->hwnd != GetDesktopWindow ()))
  gdk_window_destroy_notify (window);

return_val = TRUE;
break;

then what happens in your application?


_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list

signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Michel Donais
Sorry for the delay, I wanted to make sure I thoroughly test out what's the best, now that I allowed myself to modify the GTK source.

So, the code actually works using append_event, but it might not be the best solution, as the WM_NCDESTROY event is terminal, so merely appending an operation for future consideration is probably not the best case.

I ended up modifying it to do

>   event->any.window = g_object_ref(window);
  _gdk_event_emit(event);

instead, which emits the destruction immediately, instead of having the event appended, and eventually reacting to it when the window got destroyed. In some instances, the gdk_window_destroy_notify is receiving an already destroyed event at that point, since I added a destroy-event signal handler on my side that deletes the window, so it's all good. Note: I am not certain of the g_object_ref(window) addition, if I don't put it, it seems to destroy the GdkWindow earlier than supposed, and I get a crash in do_synthesize_crossing_event, but jury's still up for debate on that.

Also, I want to make sure foreign windows are being processed as usual, so

>   return_val = (window->window_type != GDK_WINDOW_FOREIGN);

At that point, I also modified gtkmain.c so it understands we are having non-GTK windows underneath our GTK window. So I hacked through the GDK_DESTROY event, which already got in comments it's being forced in destruction by some evil outside forces to check if its window is the automatically generated root window of our environment. These might not have Gtk objects associated with them, so it's fair play to assume they should take care of themselves, even when there's a parent. Before, if the operation was done through an append, we could've had the entire hierarchy, but now, we are getting processed immediately, so it's fair play we are processing it right now, no matter the kind of window.

>    case GDK_DESTROY:
>      /* Unexpected GDK_DESTROY from the outside, ignore for
>       * child windows, handle like a GDK_DELETE for toplevels
>       */
>      if (!gtk_widget_get_parent (event_widget) || (event->any.window->parent && event->any.window->parent->window_type == GDK_WINDOW_ROOT))

Thanks for the help. Hopefully I will be able to make my thing work without hacking off gtk too much, goal is to be as standard as possible. My code still is buggy as hell, but at least, it seems like this is a great start.


On Tue, Sep 18, 2018 at 12:54 PM LRN via gtk-list <[hidden email]> wrote:
On 18.09.2018 19:38, Michel Donais wrote:
> You are right, this is on GTK3. Hopefully, once GTK4 becomes predominant,
> it might not be useful to have foreign windows. But TBD!
>
> The problem is as follows:
>
>>      if ((window != NULL) && (msg->hwnd != GetDesktopWindow ()))
>> gdk_window_destroy_notify (window);
>
> This line actually destroys the window, so GDK_WINDOW_DESTROYED becomes
> true at that point. This is due to:
>
>> static void gdk_win32_window_destroy_notify (GdkWindow *window)
>
> where
>
>> if (!GDK_WINDOW_DESTROYED (window)),
>
> it
>
>>       _gdk_window_destroy (window, TRUE);
>
> So the line after, once the window is actually destroyed, it breaks
>
>>      if (window == NULL || GDK_WINDOW_DESTROYED (window))
>> break;
>
> and never enters the following lines.
>
>>      event = gdk_event_new (GDK_DESTROY);
>>      event->any.window = window;
>>
>>      _gdk_win32_append_event (event);
>

I see. I've looked at X11 backend, and it does things a bit differently - it
always queues a GDK_DESTROY event (due to how event processing is being done in
X11 - any notification results in an event, it seems), while also maybe calling
destory_notify() in the process.

If you rewrite WM_NCDESTROY handling to:

if ((pointer_grab != NULL && pointer_grab -> window == window) ||
    (keyboard_grab && keyboard_grab -> window == window))
  {
    GdkDevice *device = gdk_device_manager_get_client_pointer (device_manager);
    gdk_device_ungrab (device, msg -> time);
  }

if (window != NULL)
  {
    event = gdk_event_new (GDK_DESTROY);
    event->any.window = window;

    _gdk_win32_append_event (event);
  }

if ((window != NULL) && (msg->hwnd != GetDesktopWindow ()))
  gdk_window_destroy_notify (window);

return_val = TRUE;
break;

then what happens in your application?

_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list


_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Gtk+ - General mailing list
On 19.09.2018 19:40, Michel Donais wrote:
> So, the code actually works using append_event, but it might not be the
> best solution, as the WM_NCDESTROY event is terminal, so merely appending
> an operation for future consideration is probably not the best case.
>

This got me thinking: why exactly does GTK not handle WM_DESTROY? That is a
message that child windows get before they are destroyed, whereas WM_NCDESTROY
is only sent after everything is supposed to be dead.

According to commit 820721500b4f215eebda1f2466bf4166922bfa1e, this was done
deliberately.

I've looked at XOrg[1] manual, and it seems to agree with that - DestroyNotify
is called bottom-up (children first, parent last), and the X11 window can't be
referenced after that message is received (or processed?).

So yeah - it kind of works just like WM_NCDESTROY.

After that i've looked at gdk_x11_window_destroy_notify().
It checks for the GDK window not being destroyed yet, and if it isn't, the
window is destroyed (it warns if this is done for a non-foreign window), then
detaches the GDK window from GDK display, clears its grab, if any, and unrefs it.

The GDK_WINDOW_DESTROYED() (the ->destroyed field) is weird. It is only set to
TRUE by _gdk_window_destroy_hierarchy(), which sets it *after* it calls
impl->destroy() (which results in XDestroyWindow on X11; i.e. normal windows
are supposed to be XDestroyWindow-ed first, which will cause DestroyNotify,
which will be handled in gdk_x11_window_destroy_notify(), and only after that
the ->destroyed field will be set to TRUE, which will allow
GDK_WINDOW_DESTROYED() to evaluate to TRUE). I don't understand why
gdk_x11_window_destroy_notify() doesn't warn about non-foreign windows being
destroyed all the time.

W32 backend does it differently - it explicitly sets ->destroyed to TRUE before
calling DestroyWindow().

What a mess...

[1]: https://www.x.org/archive/X11R7.5/doc/man/man3/XDestroyWindow.3.html


_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list

signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Win32 WM_NCDESTROY to GDK Window to GTK Widget signal

Michel Donais
Inline answers.


On Wed, Sep 19, 2018 at 5:56 PM LRN via gtk-list <[hidden email]> wrote:
On 19.09.2018 19:40, Michel Donais wrote:
> So, the code actually works using append_event, but it might not be the
> best solution, as the WM_NCDESTROY event is terminal, so merely appending
> an operation for future consideration is probably not the best case.
>

This got me thinking: why exactly does GTK not handle WM_DESTROY? That is a
message that child windows get before they are destroyed, whereas WM_NCDESTROY
is only sent after everything is supposed to be dead.

Yep, hence the reason why I'm being more evil to the action, by actually deleting the object immediately instead of doing it eventually.

Window destruction is pretty much a backwards chicken-and-egg issue and I do understand why it would be different per platform, as these are time and context-specific operations, changing how it works depending on the platform, but you pretty much want to have a seamless integration depending if you are in X or Win32 or Mac or whatnot. So it's the kind of thing that will give you a sure headache creating a normalized experience, and will probably end up pissing off more people than it is supposed. Delay it and have the wrath of people who need to react immediately to the user interface change, or react later and feel the wrath of people trying to do things synchronously when they should be doing most of it asynchronously. And what about queued operations.


After that i've looked at gdk_x11_window_destroy_notify().
It checks for the GDK window not being destroyed yet, and if it isn't, the
window is destroyed (it warns if this is done for a non-foreign window), then
detaches the GDK window from GDK display, clears its grab, if any, and unrefs it.

The GDK_WINDOW_DESTROYED() (the ->destroyed field) is weird. It is only set to
TRUE by _gdk_window_destroy_hierarchy(), which sets it *after* it calls
impl->destroy() (which results in XDestroyWindow on X11; i.e. normal windows
are supposed to be XDestroyWindow-ed first, which will cause DestroyNotify,
which will be handled in gdk_x11_window_destroy_notify(), and only after that
the ->destroyed field will be set to TRUE, which will allow
GDK_WINDOW_DESTROYED() to evaluate to TRUE). I don't understand why
gdk_x11_window_destroy_notify() doesn't warn about non-foreign windows being
destroyed all the time.

(all of this IMHO - not an GDK X expert) I think the piece of code assumes you are using a GTK window, which asks for deletion on its own first, and then, sends the event to the system. Also, the Gdk system grabs the hierarchical operation first, and proceeds to do it hierarchically on its own from there. To have the system begging to destroy something internally without GDK knowing about it and having called it first would be something quite odd indeed. So by having the foreign window check, you can say "yeah, this is a foreign window, it's not ours, so it's normal it's being destroyed through system first, and then we get the notification", where a non-foreign window would mean someone meddling in Gdk's own business.

At least, this is what is supposed to be seen in GtkMain.c, where the warning tells you this Destroy operation should never happen, as it should've already happened for non-foreign windows.

 
W32 backend does it differently - it explicitly sets ->destroyed to TRUE before
calling DestroyWindow().

From what I've seen in systems, usually, windows will react to being destroyed instead of telling it will be destroyed. On my internal system, my first crude implementation assumed exactly that: calling an operation should do the operation. I ended up going to the standardized way of doing things in CWnd, where any operation is done through events instead of done through direct calls.

That is especially true with the window destruction, where all operations are done in WM_DESTROY and WM_NCDESTROY, and WM_NOTIFY for child destruction.

I assume the piece of code is put there because Windows can sometimes decide to delay its operations, especially if our child window happens to be owned by another thread. So the queue might not be empty at that point. I can't tell for sure, but the idea of specifically setting ->destroyed at that point would make sense in a weird and odd sense of way, so more operations can know right now we got destroyed and stop even trying.

But it never is set up later on if the system decides to meddle in its Gtk operations. For me, trying to put a Layout inside an existing HWND, those meddlings are consistent alas. Even more thinking I can theoretically have a (please cry) CWnd -> GtkLayout -> CWnd parenting layout. So the child and the parent could decide to meddle. For Gtk, it's business as usual, though as it's getting a GObject (Root) -> GtkWindow (Foreign) -> GtkLayout -> GtkWidget (Foreign). As long as it understands there's Foreign operations that can happen, I'm happy. And first message was because it wasn't reacting accordingly.

 
What a mess...

It is... But then, if you code for Win32 exclusively, it kinds of make sense. If you code for X exclusively, it's about ok. But if you code for both, it will not react the same. And we need to drink a lot of courage juice to try to do the good thing.
 

_______________________________________________
gtk-list mailing list
[hidden email]
https://mail.gnome.org/mailman/listinfo/gtk-list