Skip to content

dnd: keep the drop target visible when a drag starts from a window - #3852

Closed
dronov-dmitry wants to merge 1 commit into
linuxmint:masterfrom
dronov-dmitry:dnd-keep-drop-target-visible
Closed

dronov-dmitry wants to merge 1 commit into
linuxmint:masterfrom
dronov-dmitry:dnd-keep-drop-target-visible

Conversation

@dronov-dmitry

@dronov-dmitry dronov-dmitry commented Oct 3, 2026 •

Copy link
Copy Markdown

Summary

Starting a drag from a Nemo window that was sitting behind another window (a
terminal, typically) makes Nemo cover the window the file is being dragged to,
so the drop target disappears for as long as the drag lasts.

This undoes that raise for the duration of the drag, and hands the input focus
to the drop target once the drag has ended over some other window.

Root cause

Clicking a window that does not have the input focus is intercepted by the
window manager: Muffin raises and focuses it (its passive XIGrabButton on the
frame, meta_display_grab_focus_window_button()) and then replays the button
press to the client. So by the time Nemo sees the press, it has already been
raised above the drop target.

Nemo itself never raises its window — the only gdk_window_focus() in a drag
path is the one in start_stretching(). The raise comes entirely from the
window manager.

Behaviour after the change

Situation Result
Press on a file in a window that was in the background The raise is undone immediately, in the same event dispatch, so it is never rendered
Release without a drag (plain click) Window goes back in front, exactly as before
Press on empty space (selection rectangle) Raised again from band_select_started, which fires in the same dispatch as the press
Drag started Window stays put; the drop target stays visible
Drag ends over another window Input focus is handed to that window, so this one behaves like any other background window afterwards — the next click on it raises it again
Drag ends over Nemo itself Window is raised again
Window already had the toplevel focus Nothing happens: no raise took place, lowering it would only hide it
Desktop window (disable-chrome) Nothing happens

How it works

  • Whether the press raised the window is taken from the focus change it causes:
    nemo_window_focus_in_event() notes focus arriving while a button is down,
    which can only mean the window manager raised and focused us because of that
    click — and it is delivered before the button press itself, so
    nemo_window_dnd_source_press() can rely on it. Focus taken any other way
    (window activation, a dialog) means the window is legitimately on top. The
    flag is cleared on button release, on drag end, on focus loss and on a plain
    pointer arrival, so it never survives into the next press.

  • The position to restore is remembered as the XID of the window stacked
    directly above this one, recorded while nobody is pressing on us — the
    EnterNotify that follows the raise arrives once we are already on top, so
    recording the window above us then would record "nothing". It is restored
    with _NET_RESTACK_WINDOW
    rather than with gdk_window_lower(). Going to the bottom of the stack would
    put Nemo under windows that were below it before the click, and a drop onto
    one of its own folders would then land on one of those windows instead.

    The request is sent twice, with source indication 1 (application) and 2
    (pager): Muffin ignores anything that is not attributed to a pager
    (handle_net_restack_window()), other window managers may do the opposite,
    and the request is idempotent. Sending it as a pager is not strictly what
    EWMH intends an application to do — it is the only value Muffin accepts, and
    there is no ICCCM equivalent, because XConfigureWindow() with CWSibling
    fails with BadMatch for a reparented client window.

  • nemo_window_dnd_source_press() / nemo_window_dnd_source_release() are
    hooked to button-press-event / button-release-event of both content views;
    the icon view additionally hooks band_select_started. The list view only
    steps aside when the press lands on a row (gtk_tree_view_get_path_at_pos),
    so a selection rectangle there does not touch the window at all.

  • nemo_window_dnd_step_aside() (on drag-begin) records that a drag is
    running, re-sends the restack and falls back to gdk_window_lower() if the
    window manager does not support restacking against a sibling.

  • nemo_window_dnd_source_end() (on drag-end) activates the window under the
    pointer through _NET_ACTIVE_WINDOW, which is also what drops Nemo's focus.

Everything except the two gdk_window_* calls is behind #ifdef GDK_WINDOWING_X11; on Wayland the compositor owns stacking, so the feature is a
no-op there.

Testing

Manual, on X11 with Muffin:

  1. Put a terminal on top of Nemo, then hover a file in Nemo without
    clicking
    . Drag it onto the terminal — Nemo must not cover the terminal at
    any point, including the instant the button is pressed.
  2. Drop the file in the terminal — the terminal keeps its place on top and
    receives the input focus.
  3. Click a file without dragging — Nemo comes to the front as it always did.
  4. Press empty space and drag a selection rectangle — unchanged behaviour, the
    window stays in front.
  5. Drag a file from a background Nemo onto a folder inside the same Nemo — the
    drop lands on the folder, not on whatever window is behind Nemo.
  6. With Nemo already on top and focused, start a drag inside it — the window
    must not move at all, and the window that was behind it must stay behind it
    (an earlier revision of this branch had a regression here: a stale "this
    press raised us" flag pushed Nemo back under a window it was no longer
    under).
  7. Regular navigation, renaming (F2), double click, tabs, sidebar — unaffected.

Build and test:

meson setup builddir
ninja -C builddir
meson test -C builddir
./builddir/src/nemo --no-desktop

Affected files

File Change
src/nemo-window.c Event handlers, restack helpers, dnd_step_aside / dnd_source_press / dnd_source_release / dnd_source_end
src/nemo-window.h Four public function declarations
src/nemo-window-private.h Four new fields in NemoWindowDetails
src/nemo-icon-view.c drag-begin, drag-end, button-press-event, button-release-event, band_select_started hooks
src/nemo-list-view.c The same hooks on the tree view
src/meson.build Add the x11 dependency for XQueryPointer() / XGetWindowProperty()

Clicking a Nemo window that does not have the input focus makes the
window manager raise and focus it, so starting a drag from a window
that sat behind another one - a terminal, say - covers the window the
file is being dropped on for as long as the drag lasts.

Undo that raise from the button press handler of the content views,
i.e. in the same event dispatch the raise happened in, so it is never
rendered, and only put the window back in front if the press turns out
to be a plain click or a selection rectangle rather than a drag.

The window is put back exactly where it was instead of at the bottom of
the stack, by asking the window manager to restack it below the window
that used to be above it (_NET_RESTACK_WINDOW); dropping onto one of our
own folders therefore still works. When the drag ends over some other
window, the input focus is handed to it, so afterwards this window
behaves like any other background window and the next click on it
raises it again.

Whether a click would raise the window is sampled when the pointer
enters it and when it loses focus.  Crossing events delivered while a
button is held are ignored: the raise caused by the click produces
another EnterNotify, already with the focus, which would otherwise wipe
out the answer just recorded and stop the window from stepping aside at
all.
@mtwebster

Copy link
Copy Markdown
Member

No thanks, just pin the window you want to keep on top temporarily.

@mtwebster mtwebster closed this Oct 3, 2026
@dronov-dmitry

Copy link
Copy Markdown
Author

Understood, thanks for taking a look.

For the record, in case it helps anyone landing here later: what the patch does is narrower than pinning. It only undoes the raise that the drag-initiating click itself made, and only when the window had been in front of the drop target before that click — so the stacking is left completely untouched in every other case. Pinning is a permanent stacking preference (the terminal then stays above Nemo even when that is not wanted), while this only concerns the few hundred milliseconds during which the raise hides the drop target.

There is also a symptom pinning does not address: without the fix the covering window flickers on press (it goes up when the button goes down and back down when drag-begin fires after the motion threshold), which is visible on every drag.

The branch stays at dronov-dmitry:nemo dnd-keep-drop-target-visible if anyone wants to try it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants