Intercept WM_DELETE_WINDOW on X11?
Asked Answered
C

4

18

I'd like to intercept the WM_DELETE_WINDOW message that is posted to a certain selection of windows that an application I'm writing (AllTray), so that I can act on it instead of the application receiving it. I'm currently looking at trying this at the GDK level via gdk_display_add_client_message_filter if possible, but I'd be happy with an Xlib solution if there is one as well; it seems to be possible, but I just don't seem to be understanding how I am to do it successfully.

Currently, I have two programs (written in C) that I am trying to use to get this figured out, the first one does nothing but create a window and register that it knows about WM_DELETE_WINDOW, and the second one attempts to catch that message, but seems to fail in doing so; it appears to do precisely nothing. Am I understanding the documentation wrong on this, or is there something additional that I need to be doing (or do I need to avoid using GDK entirely for this)?

The background is this: Prior to my re-write of AllTray, the way it would do things appears to be to try to intercept a mouse-click on the X button itself. For some window managers, this worked properly, for others it didn't work at all, and for others, the user had to configure it manually and instruct AllTray where the button for closing the window was. What I am looking for is a solution that doesn't involve a LD_LIBRARY_PRELOAD and will work for any window manager/application combination that conforms to the current standards and sends a WM_DELETE_WINDOW ClientMessage when the window is closed.

UPDATE: I'm still looking for an answer. The route that I am taking at the moment is to try to reparent the window and manage it myself, but I just cannot make it work. Upon reparenting, I don't seem to be able to get it back in any way. I may be missing something very fundamental, but I can't figure out how to actually make it appear it my own window again, to bring it back on the screen.

UPDATE 2: Alright, so I've hit another brick wall. The X server documentation says to set the StructureNotifyMask on the window's event mask to receive both MapNotify and ReparentNotify events. I'd be interested in receiving either. My current thinking was to create a window that served just as an event receiver, and then when I get events for interesting things, act on them by creating and reparenting. However, this simply doesn't seem to be working. The only events I actually receive are PropertyNotify events. So, this route doesn't seem to be doing very much good, either.

Copenhaver answered 21/7, 2009 at 4:53 Comment(2)
I think it might be possible by reparenting the window inside your own toplevel, and filtering what events you pass on? I don't think the way you're currently trying can work.Jaundice
Are there any downsides to doing it that way? That is, is there anything in particular that would cause that to interfere with things like XDND or whatever? Is it a portable idea (as in, it won't break applications or window managers)? I seem to be able to find very little information on that. I assume that it also means that I would have to create one new "parent" window for each new client window, correct?Copenhaver
C
6

Unfortunately, the best answer to this question is a series of non-answers; there are technically ways to accomplish it, but they all have downfalls that make them extremely impractical:

  1. Create an X11 proxy for an application, passing all X11 protocol messages back and forth between the application and the X server. The proxy would then filter out any interesting messages. The downside to this is that this is an awful lot of overhead for a single little tiny feature, and the X11 protocol is complex. There could also be unintended consequences, which makes this an even more unattractive option.
  2. Launch as a standard application that acts as an intermediary between the window manager and “interesting” client applications. This breaks some things, such as XDnD. In effect, it is not unlike the first option, except that the proxy is at the Window level as opposed to the X11 protocol level.
  3. Use the non-portable LD_PRELOAD library trick. This has several downsides:
    1. It is non-portable across dynamic linkers: not all dynamic linkers support LD_PRELOAD, even among UNIX-like systems.
    2. It is non-portable across operating systems: not all operating systems support featureful dynamic linkers.
    3. It breaks network-transparency: the shared object/dynamic link library must reside on the host as the child process that is being executed.
    4. Not all X11 applications use Xlib; it would be necessary to write one LD_PRELOAD module for each of the libraries that an application might use to talk with X11.
    5. In addition to the last point, not all applications would be susceptible to LD_PRELOAD even if they ran under a linker that supported it, because they may not use a shared object or DLL in order to communicate with X; consider, for example, a Java application which uses an X11 protocol library written in Java itself.
    6. On some UNIX-like operating systems, LD_PRELOAD libraries must be setuid/setgid if they are to be used with setuid/setgid programs. This is, of course, a potential security vulnerability.
    7. I am quite sure that are more downsides that I cannot think of.
  4. Implement an extension to the X Window system. Non-portable among X11 implementations, complex and convoluted as all get out, and absolutely out of the question.
  5. Implement extensions or plug-ins to window managers. There are as many window managers as there are opinions on window managers, and therefore this is utterly infeasible.

Ultimately, I was able to finally accomplish my goal by using a completely separate mechanism; anyone who is interested, please see the Close-to-Tray support in AllTray 0.7.5.1dev and later, including the git master branch available on github.

Copenhaver answered 29/7, 2011 at 2:42 Comment(1)
Why is writing an X extension out of the question? If people had thought like that, no one would ever have written any extension in the first place and we'd have none.Viewfinder
I
23

I don't know X11, but I googled using "Intercept WM_DELETE_WINDOW X11" as keywords. Found 17k - MarkMail and Mplayer-commits r154 - trunk/libvo. In both cases they are doing the same thing.

 /* This is used to intercept window closing requests.  */
 static Atom wm_delete_window;

within static void x11_init(),

XMapWindow(display, win);
wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, win, &wm_delete_window, 1);

then, within static int x11_check_events(),

XEvent Event;
while (XPending(display)) {
    XNextEvent(display, &Event);
    if (Event.type == ClientMessage) {
        if ((Atom)Event.xclient.data.l[0] == wm_delete_window) {
            /* your code here */
        }
    }
}

See XInternAtom, XSetWMProtocols and XNextEvent.

After I wrote the above, I found Handling window close in an X11 app:

When a user clicks the close button [x] on our X11 application we want it to pop a a dialog asking “do you really want to quit?”. This is a plain X app. No fancy GTK or QT widgets here. So how to catch the “window is being closed” message?

The answer is to tell the Window Manager we are interested in these event by calling XSetWMProtocols and registering a WM_DELETE_WINDOW message with it. Then we’ll get a client message from the Window Manager if someone tries to close the window, and it won’t close it, it’ll leave that us up to us. Here’s an example….

// example.cpp
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <iostream>

int main()
{
   Display* display = XOpenDisplay(NULL);
   Window window = XCreateSimpleWindow(display,
                                       DefaultRootWindow(display),
                                       0, 0,
                                       500, 400,
                                       0,
                                       0, 0);

   // register interest in the delete window message
   Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
   XSetWMProtocols(display, window, &wmDeleteMessage, 1);

   std::cout << "Starting up..." << std::endl;
   XMapWindow(display, window);

   while (true) {
      XEvent event;
      XNextEvent(display, &event);

      if (event.type == ClientMessage &&
          event.xclient.data.l[0] == wmDeleteMessage) {
         std::cout << "Shutting down now!!!" << std::endl;
         break;
      }
   }

   XCloseDisplay(display);
   return 0;
}
Israel answered 27/7, 2009 at 5:26 Comment(3)
That would be great... if only it solved my problem. The problem is not that I own a window and I want to receive the event; that I can do just fine (and would do one of the above ways). The problem is that my application needs to intercept that for other windows. Windows that aren't mine, that I did not create. I'm still trying to figure out what the first commenter meant by forcibly reparenting windows, but I have yet to actually get that to work. I've been googling for days. That is why I came here.Copenhaver
I am assuming you've read tronche.com/gui/x/xlib/window-and-session-manager/… already.Israel
Indeed I have. I'm still not quite sure what to do to make it work correctly; I've managed to get windows reparented, but I must be missing something because I'm not able to attain my goal of making things work. :-/ I actually have the Xlib reference on my desk, which I've been combing through repeatedly to try to figure this out. :-/Copenhaver
C
6

Unfortunately, the best answer to this question is a series of non-answers; there are technically ways to accomplish it, but they all have downfalls that make them extremely impractical:

  1. Create an X11 proxy for an application, passing all X11 protocol messages back and forth between the application and the X server. The proxy would then filter out any interesting messages. The downside to this is that this is an awful lot of overhead for a single little tiny feature, and the X11 protocol is complex. There could also be unintended consequences, which makes this an even more unattractive option.
  2. Launch as a standard application that acts as an intermediary between the window manager and “interesting” client applications. This breaks some things, such as XDnD. In effect, it is not unlike the first option, except that the proxy is at the Window level as opposed to the X11 protocol level.
  3. Use the non-portable LD_PRELOAD library trick. This has several downsides:
    1. It is non-portable across dynamic linkers: not all dynamic linkers support LD_PRELOAD, even among UNIX-like systems.
    2. It is non-portable across operating systems: not all operating systems support featureful dynamic linkers.
    3. It breaks network-transparency: the shared object/dynamic link library must reside on the host as the child process that is being executed.
    4. Not all X11 applications use Xlib; it would be necessary to write one LD_PRELOAD module for each of the libraries that an application might use to talk with X11.
    5. In addition to the last point, not all applications would be susceptible to LD_PRELOAD even if they ran under a linker that supported it, because they may not use a shared object or DLL in order to communicate with X; consider, for example, a Java application which uses an X11 protocol library written in Java itself.
    6. On some UNIX-like operating systems, LD_PRELOAD libraries must be setuid/setgid if they are to be used with setuid/setgid programs. This is, of course, a potential security vulnerability.
    7. I am quite sure that are more downsides that I cannot think of.
  4. Implement an extension to the X Window system. Non-portable among X11 implementations, complex and convoluted as all get out, and absolutely out of the question.
  5. Implement extensions or plug-ins to window managers. There are as many window managers as there are opinions on window managers, and therefore this is utterly infeasible.

Ultimately, I was able to finally accomplish my goal by using a completely separate mechanism; anyone who is interested, please see the Close-to-Tray support in AllTray 0.7.5.1dev and later, including the git master branch available on github.

Copenhaver answered 29/7, 2011 at 2:42 Comment(1)
Why is writing an X extension out of the question? If people had thought like that, no one would ever have written any extension in the first place and we'd have none.Viewfinder
J
-1

Ok, to elaborate on my earlier suggestion, you might want to investigate XEmbed. At the least, that might give you some ideas to try.

Failing that, I'd have a look at how other similar software might be working (e.g. wmdock, or how GtkPlug/GtkSocket is implemented), though I believe in both those cases explicit support is required in the applications.

Hope that is more helpful.

Jaundice answered 30/7, 2009 at 21:26 Comment(1)
I did experiment with this at one point; however, it was very very unreliable. It was so unreliable as to be unsuitable for a production program. I did fortunately find another way to accomplish the end-goal that I had in mind.Copenhaver
C
-1

You should read ICCCM that tells you how window manager communicates with client. Most of WM will create a frame window to contain your top-level window via reparenting. Thus, if your reparent may break the relationship known by WM and your client window.

Cacka answered 25/6, 2011 at 12:53 Comment(1)
Thanks. I have read the ICCCM and a fair bit more about X11. Reparenting is not an option without a lot of work. Old AllTray did reparenting trickery, which broke a fair lot of things in the process; for example, XDnD.Copenhaver

© 2022 - 2024 — McMap. All rights reserved.