window-props: Read WM_TRANSIENT_FOR for override-redirect windows
This is a continuation of !895 (merged) as per comments there (so only last commit matters).
As per the Extended Window Manager Hints standard version 1.3 [1] an override-redirect window can set a transient-for window per compositing and app-matching purposes.
So just read the WM_TRASIENT_FOR property also for such windows, adapting the error in case they are transient for another O-R window and adding a test to check such case.
[1] https://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472512128
In order to check if we didn't break anything at mutter level (but I've done something also in the shell, and I didn't find anything that could create issues), I went through all the various usage of ->transient_for
or get_transient_for
.
Here's an analysis
Mutter
All good
src/core/window.c- if (window->input_region)
src/core/window.c- cairo_region_destroy (window->input_region);
src/core/window.c-
src/core/window.c: if (window->transient_for)
src/core/window.c: g_object_unref (window->transient_for);
src/core/window.c-
src/core/window.c- g_free (window->sm_client_id);
src/core/window.c- g_free (window->wm_client_machine);
A window can't have a O-R window as parent as per this MR and !895 (merged), while if window
is an O-R, this code won't ever be called as per a previous check.
src/core/window.c- * being recorded as a fallback for potential transients
src/core/window.c- */
src/core/window.c- window->net_wm_user_time = window->initial_timestamp;
src/core/window.c: else if (window->transient_for != NULL)
src/core/window.c: meta_window_set_user_time (window, window->transient_for->net_wm_user_time);
src/core/window.c- else
src/core/window.c- /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just
src/core/window.c- * being recorded as a fallback for potential transients
This is covered already by !895 (merged)
src/core/window.c- */
src/core/window.c- if (!window->override_redirect && window->workspace == NULL)
src/core/window.c- {
src/core/window.c: if (window->transient_for != NULL)
src/core/window.c- {
src/core/window.c- meta_topic (META_DEBUG_PLACEMENT,
src/core/window.c- "Putting window %s on same workspace as parent %s\n",
src/core/window.c: window->desc, window->transient_for->desc);
src/core/window.c-
src/core/window.c: g_assert_false (window->transient_for->override_redirect);
src/core/window.c- set_workspace_state (window,
src/core/window.c: should_be_on_all_workspaces (window->transient_for),
src/core/window.c: window->transient_for->workspace);
src/core/window.c- }
src/core/window.c- else if (window->on_all_workspaces)
src/core/window.c- {
These are in meta_window_activate_full()
which is called only for non-override-redirect
windows as per meta_window_activate_*
checks.
src/core/window.c- /* For non-transient windows, we just set up a pulsing indicator,
src/core/window.c- rather than move windows or workspaces.
src/core/window.c- See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */
src/core/window.c: if (window->transient_for == NULL &&
src/core/window.c- !allow_workspace_switch &&
src/core/window.c- !meta_window_located_on_workspace (window, workspace))
src/core/window.c- {
--
src/core/window.c- /* We've marked it as demanding, don't need to do anything else. */
src/core/window.c- return;
src/core/window.c- }
src/core/window.c: else if (window->transient_for != NULL)
src/core/window.c- {
src/core/window.c- /* Move transients to current workspace - preference dialogs should appear over
src/core/window.c- the source window. */
No problem here (get_modal_transient()
)
src/core/window.c- {
src/core/window.c- MetaWindow *transient = tmp->data;
src/core/window.c-
src/core/window.c: if (transient->transient_for == modal_transient &&
src/core/window.c- transient->type == META_WINDOW_MODAL_DIALOG)
src/core/window.c- {
src/core/window.c- modal_transient = transient;
No problem here (meta_window_recalc_skip_features()
), since in such case it
would be marked as META_WINDOW_OVERRIDE_OTHER
so this code wouldn't be either
reached.
src/core/window.c- case META_WINDOW_MODAL_DIALOG:
src/core/window.c- /* only skip taskbar if we have a real transient parent
src/core/window.c- (and ignore the application hints) */
src/core/window.c: if (window->transient_for != NULL)
src/core/window.c- window->skip_taskbar = TRUE;
src/core/window.c- else
src/core/window.c- window->skip_taskbar = FALSE;
meta_window_foreach_ancestor()
is fine as well
src/core/window.c- w = window;
src/core/window.c- do
src/core/window.c- {
src/core/window.c: if (w->transient_for == NULL)
src/core/window.c- break;
src/core/window.c-
src/core/window.c: w = w->transient_for;
src/core/window.c- }
src/core/window.c- while (w && (* func) (w, user_data));
src/core/window.c-}
meta_window_get_transient_for()
should of course return it anyways, and in fact
we mostly use this for matching purposes around (i.e. in both stack and shell
tracker which we want)
src/core/window.c-{
src/core/window.c- g_return_val_if_fail (META_IS_WINDOW (window), NULL);
src/core/window.c-
src/core/window.c: if (window->transient_for)
src/core/window.c: return window->transient_for;
src/core/window.c- else if (window->xtransient_for)
src/core/window.c- return meta_x11_display_lookup_x_window (window->display->x11_display,
src/core/window.c- window->xtransient_for);
check_transient_for_loop()
, this is fine to have (although, repeated)
src/core/window.c- {
src/core/window.c- if (parent == window)
src/core/window.c- return TRUE;
src/core/window.c: parent = parent->transient_for;
src/core/window.c- }
src/core/window.c-
src/core/window.c- return FALSE;
This is all part of meta_window_set_transient_for()
and there's nothing wrong
in case the current window is an O-R, and we already protect in such case the
calls to meta_stack_update_transient()
and meta_window_queue()
src/core/window.c- return;
src/core/window.c- }
src/core/window.c-
src/core/window.c: if (meta_window_appears_focused (window) && window->transient_for != NULL)
src/core/window.c- meta_window_propagate_focus_appearance (window, FALSE);
src/core/window.c-
src/core/window.c- /* may now be a dialog */
--
src/core/window.c- * parents, we need to destroy the MetaWindow and let a new one
src/core/window.c- * be created (which happens as a side effect of
src/core/window.c- * meta_window_unmanage()). The condition below is correct
src/core/window.c: * because we know window->transient_for has changed.
src/core/window.c- */
src/core/window.c- if (window->attached || meta_window_should_attach_to_parent (window))
src/core/window.c- {
--
src/core/window.c- return;
src/core/window.c- }
src/core/window.c- /* We know this won't create a reference cycle because we check for loops */
src/core/window.c: g_clear_object (&window->transient_for);
src/core/window.c: window->transient_for = parent ? g_object_ref (parent) : NULL;
src/core/window.c-
src/core/window.c- /* update stacking constraints */
src/core/window.c- if (!window->override_redirect)
--
src/core/window.c- if (!window->constructing && !window->override_redirect)
src/core/window.c- meta_window_queue (window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING);
src/core/window.c-
src/core/window.c: if (meta_window_appears_focused (window) && window->transient_for != NULL)
src/core/window.c- meta_window_propagate_focus_appearance (window, TRUE);
src/core/window.c-}
src/core/window.c-
Not applies here
src/core/meta-close-dialog-default.c- {
src/core/meta-close-dialog-default.c- MetaWindow *w = tmp->data;
src/core/meta-close-dialog-default.c-
src/core/meta-close-dialog-default.c: if (w->transient_for == window && w->res_class &&
src/core/meta-close-dialog-default.c- g_ascii_strcasecmp (w->res_class, "mutter-dialog") == 0)
src/core/meta-close-dialog-default.c- {
src/core/meta-close-dialog-default.c- meta_window_activate (w, CLUTTER_CURRENT_TIME);
OR windows aren't stacked anyways
src/core/stack.c- w->type == META_WINDOW_UTILITY)
src/core/stack.c-
src/core/stack.c-#define WINDOW_TRANSIENT_FOR_WHOLE_GROUP(w) \
src/core/stack.c: (WINDOW_HAS_TRANSIENT_TYPE (w) && w->transient_for == NULL)
src/core/stack.c-
src/core/stack.c-static void meta_window_set_stack_position_no_sync (MetaWindow *window,
src/core/stack.c- int position);
--
src/core/stack.c- */
src/core/stack.c- if (window->layer != META_LAYER_DESKTOP &&
src/core/stack.c- WINDOW_HAS_TRANSIENT_TYPE(window) &&
src/core/stack.c: window->transient_for == NULL)
src/core/stack.c- {
src/core/stack.c- /* We only do the group thing if the dialog is NOT transient for
src/core/stack.c- * a particular window. Imagine a group with a normal window, a dock,
--
src/core/stack.c-
src/core/stack.c- g_slist_free (group_windows);
src/core/stack.c- }
src/core/stack.c: else if (w->transient_for != NULL)
src/core/stack.c- {
src/core/stack.c- MetaWindow *parent;
src/core/stack.c-
src/core/stack.c: parent = w->transient_for;
src/core/stack.c-
src/core/stack.c- if (parent && meta_window_is_in_stack (parent))
src/core/stack.c- {
This handles later alrady the case of override_redirect windows, and in such
case, the window will be marked as META_WINDOW_OVERRIDE_OTHER
, so nothing
to worry about.
src/x11/window-x11.c- XFree (atom_name);
src/x11/window-x11.c- }
src/x11/window-x11.c- }
src/x11/window-x11.c: else if (window->transient_for != NULL)
src/x11/window-x11.c- {
src/x11/window-x11.c- type = META_WINDOW_DIALOG;
src/x11/window-x11.c- }
meta_window_should_attach_to_parent()
, we'd alread returned before this if
the window was an O-R (i.e not a dialog).
src/core/window.c- window->type != META_WINDOW_MODAL_DIALOG)
src/core/window.c- return FALSE;
src/core/window.c-
src/core/window.c: parent = meta_window_get_transient_for (window);
src/core/window.c- if (!parent)
src/core/window.c- return FALSE;
src/core/window.c-
This is never happening again, since meta_window_should_attach_to_parent()
Won't return TRUE for O-R windows.
src/core/window.c- if (window->attached)
src/core/window.c- {
src/core/window.c- /* Only return the immediate children of the window being unmanaged */
src/core/window.c: parent = meta_window_get_transient_for (window);
src/core/window.c- if (parent->unmanaging)
src/core/window.c- *children = g_list_prepend (*children, window);
src/core/window.c- }
meta_window_propagate_focus_appearance()
, don't seem to be wrong again
src/core/window.c- focus_window = window->display->focus_window;
src/core/window.c-
src/core/window.c- child = window;
src/core/window.c: parent = meta_window_get_transient_for (child);
src/core/window.c- while (parent && (!focused || should_propagate_focus_appearance (child)))
src/core/window.c- {
src/core/window.c- gboolean child_focus_state_changed;
--
src/core/window.c- }
src/core/window.c-
src/core/window.c- child = parent;
src/core/window.c: parent = meta_window_get_transient_for (child);
src/core/window.c- }
src/core/window.c-}
src/core/window.c-
meta_window_process_placement()
called only if window has
window->placement_rule set, never true for O-R windows.
src/core/place.c- int *x,
src/core/place.c- int *y)
src/core/place.c-{
src/core/place.c: MetaWindow *parent = meta_window_get_transient_for (window);
src/core/place.c- MetaRectangle parent_rect;
src/core/place.c- MetaRectangle anchor_rect;
src/core/place.c- int window_width, window_height;
No such window type in any O-R window ever.
src/core/place.c- if (window->type == META_WINDOW_DIALOG ||
src/core/place.c- window->type == META_WINDOW_MODAL_DIALOG)
src/core/place.c- {
src/core/place.c: MetaWindow *parent = meta_window_get_transient_for (window);
src/core/place.c-
src/core/place.c- if (parent)
src/core/place.c- {
constrain_modal_dialog()
, we'd have already returned here if not a dialog
src/core/constraints.c-
src/core/constraints.c- adjusted_unconstrained = info->current;
src/core/constraints.c-
src/core/constraints.c: parent = meta_window_get_transient_for (window);
src/core/constraints.c- meta_window_get_frame_rect (parent, &parent_rect);
src/core/constraints.c-
src/core/constraints.c- switch (window->placement_state)
constrain_custom_rule()
, again not possible to have a placement rule set
src/core/constraints.c- gboolean check_only)
src/core/constraints.c-{
src/core/constraints.c- int x, y;
src/core/constraints.c: MetaWindow *parent = meta_window_get_transient_for (window);
src/core/constraints.c- MetaRectangle child_rect, parent_rect;
src/core/constraints.c- gboolean constraint_already_satisfied;
src/core/constraints.c-
called by meta_display_begin_grab_op()
, O-R windows should not be called for
such function anyways, but in any case meta_window_is_attached_dialog()
would
not return TRUE for a such window.
src/core/display.c-get_first_freefloating_window (MetaWindow *window)
src/core/display.c-{
src/core/display.c- while (meta_window_is_attached_dialog (window))
src/core/display.c: window = meta_window_get_transient_for (window);
src/core/display.c-
src/core/display.c- /* Attached dialogs should always have a non-NULL transient-for */
src/core/display.c- g_assert (window != NULL);
Shell
No applies here
./js/ui/altTab.js- workspace);
./js/ui/altTab.js- // ... map windows to their parent where appropriate ...
./js/ui/altTab.js- return windows.map(w => {
./js/ui/altTab.js: return w.is_attached_dialog() ? w.get_transient_for() : w;
./js/ui/altTab.js- // ... and filter out skip-taskbar windows and duplicates
./js/ui/altTab.js- }).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) == i);
./js/ui/altTab.js-}
All good here too
./js/ui/workspaceThumbnail.js- if (this._isOverviewWindow(win)) {
./js/ui/workspaceThumbnail.js- this._addWindowClone(win);
./js/ui/workspaceThumbnail.js- } else if (metaWin.is_attached_dialog()) {
./js/ui/workspaceThumbnail.js: let parent = metaWin.get_transient_for();
./js/ui/workspaceThumbnail.js- while (parent.is_attached_dialog())
./js/ui/workspaceThumbnail.js: parent = parent.get_transient_for();
./js/ui/workspaceThumbnail.js-
./js/ui/workspaceThumbnail.js- let idx = this._lookupIndex(parent);
./js/ui/workspaceThumbnail.js- if (idx < 0) {
Ditto
./js/ui/workspace.js- }
./js/ui/workspace.js-
./js/ui/workspace.js- addDialog(win) {
./js/ui/workspace.js: let parent = win.get_transient_for();
./js/ui/workspace.js- while (parent.is_attached_dialog())
./js/ui/workspace.js: parent = parent.get_transient_for();
./js/ui/workspace.js-
./js/ui/workspace.js- // Display dialog if it is attached to our metaWindow
./js/ui/workspace.js- if (win.is_attached_dialog() && parent == this.metaWindow) {
--
./js/ui/workspace.js- return;
./js/ui/workspace.js-
./js/ui/workspace.js- if (!this._isOverviewWindow(win)) {
./js/ui/workspace.js: if (metaWin.get_transient_for() == null)
./js/ui/workspace.js- return;
./js/ui/workspace.js-
./js/ui/workspace.js- // Let the top-most ancestor handle all transients
Don't see issues here as well
./js/ui/windowManager.js- windows.forEach(window => {
./js/ui/windowManager.js- // If the window is attached to an ancestor, we don't need/want
./js/ui/windowManager.js- // to move it
./js/ui/windowManager.js: if (window.get_transient_for() != null)
./js/ui/windowManager.js- return;
./js/ui/windowManager.js- // Same for OR windows
./js/ui/windowManager.js- if (window.is_override_redirect())
--
./js/ui/windowManager.js- window.foreach_transient(win => {
./js/ui/windowManager.js- if (win != ignoreWindow &&
./js/ui/windowManager.js- win.is_attached_dialog() &&
./js/ui/windowManager.js: win.get_transient_for() == window) {
./js/ui/windowManager.js- count++;
./js/ui/windowManager.js- return false;
./js/ui/windowManager.js- }
--
./js/ui/windowManager.js- return;
./js/ui/windowManager.js- if (type == Meta.WindowType.MODAL_DIALOG ||
./js/ui/windowManager.js- actor._windowType == Meta.WindowType.MODAL_DIALOG) {
./js/ui/windowManager.js: let parent = actor.get_meta_window().get_transient_for();
./js/ui/windowManager.js- if (parent)
./js/ui/windowManager.js- this._checkDimming(parent);
./js/ui/windowManager.js- }
--
./js/ui/windowManager.js- actor._windowType = type;
./js/ui/windowManager.js- });
./js/ui/windowManager.js- actor.meta_window.connect('unmanaged', window => {
./js/ui/windowManager.js: let parent = window.get_transient_for();
./js/ui/windowManager.js- if (parent)
./js/ui/windowManager.js- this._checkDimming(parent);
./js/ui/windowManager.js- });
./js/ui/windowManager.js-
./js/ui/windowManager.js- if (actor.meta_window.is_attached_dialog())
./js/ui/windowManager.js: this._checkDimming(actor.get_meta_window().get_transient_for());
./js/ui/windowManager.js-
./js/ui/windowManager.js- let types = [Meta.WindowType.NORMAL,
./js/ui/windowManager.js- Meta.WindowType.DIALOG,
--
./js/ui/windowManager.js- }
./js/ui/windowManager.js-
./js/ui/windowManager.js- if (window.is_attached_dialog())
./js/ui/windowManager.js: this._checkDimming(window.get_transient_for(), window);
./js/ui/windowManager.js-
./js/ui/windowManager.js- let types = [Meta.WindowType.NORMAL,
./js/ui/windowManager.js- Meta.WindowType.DIALOG,
--
./js/ui/windowManager.js- this._destroying.push(actor);
./js/ui/windowManager.js-
./js/ui/windowManager.js- if (window.is_attached_dialog()) {
./js/ui/windowManager.js: let parent = window.get_transient_for();
./js/ui/windowManager.js- actor._parentDestroyId = parent.connect('unmanaged', () => {
./js/ui/windowManager.js- actor.remove_all_transitions();
./js/ui/windowManager.js- this._destroyWindowDone(shellwm, actor);
--
./js/ui/windowManager.js-
./js/ui/windowManager.js- _destroyWindowDone(shellwm, actor) {
./js/ui/windowManager.js- if (this._removeEffect(this._destroying, actor)) {
./js/ui/windowManager.js: let parent = actor.get_meta_window().get_transient_for();
./js/ui/windowManager.js- if (parent && actor._parentDestroyId) {
./js/ui/windowManager.js- parent.disconnect(actor._parentDestroyId);
./js/ui/windowManager.js- actor._parentDestroyId = 0;
--
./src/shell-window-tracker.c- MetaWindow *transient_for;
./src/shell-window-tracker.c- const char *startup_id;
./src/shell-window-tracker.c-
./src/shell-window-tracker.c: transient_for = meta_window_get_transient_for (window);
./src/shell-window-tracker.c- if (transient_for != NULL)
./src/shell-window-tracker.c- return get_app_for_window (tracker, transient_for);
./src/shell-window-tracker.c-
--
./src/shell-window-tracker.c- * - 'nautilus' should not appear focused when the DESKTOP has focus
./src/shell-window-tracker.c- */
./src/shell-window-tracker.c- while (new_focus_win && meta_window_is_skip_taskbar (new_focus_win))
./src/shell-window-tracker.c: new_focus_win = meta_window_get_transient_for (new_focus_win);
./src/shell-window-tracker.c-
./src/shell-window-tracker.c-