The overview series: Drag and drop. You complain, we explain.

drag onIf there are two overlapping windows on the screen, people would like to be able to pick up an object from the lower window and drag it to the upper without bringing the lower window to the front, because if that happens the lower window will obscure the upper, and you won’t have anywhere to drag to. In this instance, we would like the same behaviour as Microsoft Windows: if the click starts a drag, raise the lower window on button release; if it doesn’t, raise the window on button press as normal.

However, Metacity (along with most other window managers) doesn’t currently do this, for want of a way to know whether the click starts a drag. This is really something that only the application owning that window can tell us. (It is possible for the user to tell us what they think, by holding down AltGr at the start of the drag. That may not be an official feature. It’s not really ideal either way.)

This whole question is something we’ve been batting around for six years now and it probably ought to be fixed one way or another. Over that time, there are also a few other reasons people have asked to be able to pick stuff up from lower windows, such as the ability to copy text from the lower window and paste it into the upper, or scrolling the lower window’s scrollbars : GNOME bug 76672 deals with this more general case, which we shan’t discuss further here now. Let’s concentrate on the most common problem, represented by GNOME bug 80984: not raising the source window when a drag and drop begins. What isn’t a solution to our problem?

What isn’t a solution

  • Always raising the lower window only on release, not on click (suggested by many people). This would solve the problem at the cost of weirding everyone out, not just breaking the expectations of existing Metacity users and users from other window managers in the world of free software, but also the expectations of Mac and Windows people.
  • Only raising a window when you click on the frame and not the insides, which was raised in GNOME bug 86108. This is a bad idea for similar reasons to the last.
  • Having a magic kind of window that Metacity promises never to raise; then the client will decide whether to raise itself or not based on whether the click was the start of a drag operation. This is how Sawfish does or did it. It’s a bad idea because it rather defeats the purpose of having a window manager if clients are going to manage their own windows, and besides applications can’t raise their own windows in Metacity anyway.

What is a solution
What needs to happen is this:

  1. We figure out a way for other clients to tell the window manager that a click in their window was the start of some kind of drag-and-drop operation.
  2. At this point, the fact that Metacity doesn’t understand this message suddenly becomes a bug in Metacity. So we fix the window manager to understand this.
  3. At this point, the fact that none of the applications out there understand how to tell Metacity about this becomes a bug in those applications, but we can’t do anything much about it without fixing the toolkits like GTK. So we do that.
  4. Now we can actually fix all the applications separately. The bug for fixing Nautilus at this point is GNOME bug 132339.

Clearly we can’t get 2, 3, and 4 sorted until we have 1 down, so let’s just talk about that for the moment. Back in 2004, Lubos Lunak (the maintainer of KDE’s window manager) proposed the first plan to do this, called _NET_WM_TAKE_ACTIVITY (a misleading name, since it’s about taking focus and not activity). When a window other than the topmost one was clicked, the window manager would send it _NET_WM_TAKE_ACTIVITY, which it would remember; after that, nothing would happen until the button was released. If the click had actually begun a drag-and-drop operation, that was all well and good, but if it hadn’t, the client should send it on to the root window and the window manager would raise the window after all. In GNOME bug 152952, Elijah Newren wrote a patch for Metacity implementing this plan.

Lubos’s original plan had a few infelicities, some of which were discussed in this meeting. It means that the window is raised when you release the mouse button, which is bad for reasons we discussed above.  It also means that a lot of policy is decided ahead of time: for example, some people would like their window manager to raise the lower window while they were copying text from it, and then drop it back down when they were done, but not do the same thing for drag-and-drop.  There was working code for KDE and GNOME, but many people objected about all the problems mentioned above including the GTK hackers.  In the end it didn’t make it into the EWMH standard, although some parts of the KDE libraries appear still to accept it to some extent.

Elijah then proposed to fix the problem with a new message type called _NET_WM_MOUSE_ACTION. With this plan, a client would send _NET_WM_MOUSE_ACTION through to the root window as soon as any button was pressed or released on it, telling the window manager what kind of action the click meant: it could be “nothing special” or “drag-and-drop”, but also “text selection” or “scrollbar drag” or “generic thing that I don’t want to explain right now but involves not raising me”.  Lubos agreed that this was a better plan, but it died even earlier in committee, and as far as I know was never implemented anywhere.

It seems to me that the best thing to do, if we can, is to go with a partial fix using _NET_WM_MOUSE_ACTION which allows us to heal this obvious problem.  Then we can carry on later and fix specific problems.  Elijah has said that _NET_WM_MOUSE_ACTION needed a great deal of work to implement on the GTK side; the closest thing we have so far to working code is a patch he then posted.  This does still need working on, preferably by someone who understands the internals of GDK (could this be you, gentle reader?).

A similar but not identical problem is the issue of raising windows when they are a drag target; this is covered in GNOME bug 112308.

Next in the overview series: why getting stacking exactly right is hard and what we’re going to do about it.

Photo by pbo31, cc-by-nc-nd.

Version numbers (and automatic hyperlinking)

A short note about Metacity’s version numbers: they are of the form major.minor.point, as with most software these days. “major.minor” tracks the GNOME release they belong to; thus major will probably be 2 indefinitely and minor will increase by 2 every six months, using the convention laid down by the Linux kernel of using the odd number as the unstable branch and the even number one lower as the stable version into which only bugfixes will go. The first version of Metacity was version 2.3.

The point numbers mark individual releases; they iterate over the Fibonacci series, beginning at F2. (The point numbers of GNOME as a whole iterate over the natural numbers.) There should be at least one Metacity release just before each GNOME release, and at least one every fortnight or so. The main version currently under development (“trunk”, in Subversion language) identifies itself with the version number of the next unstable point release.

Automatic hyperlinking: I will be writing a script which takes text about the day and posts it here. As part of that, I would like to hyperlink names of contributors (including translators!) If you’re likely to be mentioned here and you have a website, let me know what it is.

How Metacity creates an alt-Tab list

1926PepFrenchNovoTabsI’ve been asked to explain how to add new items to the list of programs which appears when you use alt-Tab to switch between programs in Metacity. This is a bit of a simplification, but it goes like this:

When the user is switching between applications using alt-Tab, Metacity has what’s called a “keyboard grab” on the X server. This means that it gets told about all the keyboard events, and no other X client hears about them. The grab is initiated by meta_display_begin_grab_op() and ended by meta_display_end_grab_op().

Everything Metacity needs to know about a screen’s tab popup is kept in a MetaTabPopup called “tab_popup” which lives inside the MetaScreen representing that screen. In a newly-created MetaScreen, it’s null, because there’s no popup on that screen. It gets initialised by meta_display_begin_grab_op() calling meta_screen_ensure_tab_popup() (or meta_screen_ensure_workspace_popup() if we’re actually needing to switch between workspaces, rather than applications), both of which call meta_ui_tab_popup_new() in order to actually allocate and initialise the memory.

Before it calls meta_ui_tab_popup_new(), however, meta_screen_ensure_tab_popup() needs to prepare a list of MetaTabEntry items to display in the popup. It does this by calling the function meta_display_get_tab_list(), which is where the magic truly lives.

meta_display_get_tab_list() returns a GList of MetaWindow objects. (meta_screen_ensure_tab_popup() converts the MetaWindows to MetaTabEntrys.) This is where the actual list of windows that you see when you press alt-Tab comes from.

Now, there are actually three kinds of list that meta_display_get_tab_list() might be asked for, and these are represented by the possible values of the MetaTabList enum parameter “type”. META_TAB_LIST_NORMAL means the ordinary alt-Tab sequence you’re used to, of ordinary user application windows. META_TAB_LIST_DOCKS means that what’s needed is a list of system windows; you can see this in action by pressing alt-ctrl-Tab. META_TAB_LIST_GROUP means that what’s needed is a list of all windows (even dialogues, etc.) in the same application group as the current window.

How do we deal with this headache-inducing complexity? meta_display_get_tab_list() does the sensible thing and delegates it (in this case, to a macro). It iterates over every window in the requested workspace and adds it to the result list if

  • it’s on the same screen, and
  • the IN_TAB_CHAIN macro returns true for it. We will come back to this in a moment.

The result list is ordered firstly by whether the window’s minimised (minimised windows come first, presumably because this is standard behaviour on certain other operating systems); after that, most recently used windows come first. You can verify this using alt-Tab on your own desktop.

The IN_TAB_CHAIN macro (in display.c) is written in terms of the three macros META_WINDOW_IN_*_TAB_CHAIN (in window.h). Each of these takes a single argument, a MetaWindow, and returns true or false depending on whether it’s in that particular tab chain.

The original reason I was asked to explain all this was a request to add all windows (on any workspace) which have the urgency hint to any newly-created tab chain. This wouldn’t be a suitable thing to add to the META_WINDOW_IN_*_TAB_CHAIN macros, because the list of windows passed to those is pre-filtered to include only windows on the current workspace. Instead, it should be added to meta_display_get_tab_list() as a separate, final step.