[mutter] When monitors change, keep windows on same output.



commit 19b6888ea574f1b8c17b49f09a8cfcad8142285b
Author: Alexander Larsson <alexl redhat com>
Date:   Fri May 20 10:56:12 2011 +0200

    When monitors change, keep windows on same output.
    
    If XRANDR is availible, we track the first (or primary) output per
    crtc (== xinerama monitor) so when the monitors change we can try
    to find the same output and move windows there. If we can't find the
    original monitor in the new set (or XRANDR is not supported) we move
    the window to the primary monitor.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=645408

 src/core/screen-private.h |    2 +
 src/core/screen.c         |  118 +++++++++++++++++++++++++++++++++++----------
 src/core/window-private.h |    2 +-
 src/core/window.c         |   94 +++++++++++++++++++++++++++--------
 4 files changed, 168 insertions(+), 48 deletions(-)
---
diff --git a/src/core/screen-private.h b/src/core/screen-private.h
index acb826a..866f5fb 100644
--- a/src/core/screen-private.h
+++ b/src/core/screen-private.h
@@ -45,6 +45,8 @@ struct _MetaMonitorInfo
 {
   int number;
   MetaRectangle rect;
+  gboolean is_primary;
+  XID output; /* The primary or first output for this crtc, None if no xrandr */
 };
 
 typedef void (* MetaScreenWindowFunc) (MetaScreen *screen, MetaWindow *window,
diff --git a/src/core/screen.c b/src/core/screen.c
index c6cc99e..020940b 100644
--- a/src/core/screen.c
+++ b/src/core/screen.c
@@ -48,6 +48,9 @@
 #ifdef HAVE_XFREE_XINERAMA
 #include <X11/extensions/Xinerama.h>
 #endif
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
 
 #include <X11/Xatom.h>
 #include <locale.h>
@@ -386,6 +389,56 @@ filter_mirrored_monitors (MetaScreen *screen)
     }
 }
 
+#ifdef HAVE_RANDR
+static MetaMonitorInfo *
+find_monitor_with_rect (MetaScreen *screen, int x, int y, int w, int h)
+{
+  MetaMonitorInfo *info;
+  int i;
+
+  for (i = 0; i < screen->n_monitor_infos; i++)
+    {
+      info = &screen->monitor_infos[i];
+      if (x == info->rect.x &&
+          y == info->rect.y &&
+          w == info->rect.width &&
+          h == info->rect.height)
+        return info;
+    }
+  return NULL;
+}
+
+/* In the case of multiple outputs of a single crtc (mirroring), we consider one of the
+ * outputs the "main". This is the one we consider "owning" the windows, so if
+ * the mirroring is changed to a dual monitor setup then the windows are moved to the
+ * crtc that now has that main output. If one of the outputs is the primary that is
+ * always the main, otherwise we just use the first.
+ */
+static XID
+find_main_output_for_crtc (MetaScreen *screen, XRRScreenResources *resources, XRRCrtcInfo *crtc)
+{
+  XRROutputInfo *output;
+  RROutput primary_output;
+  int i;
+  XID res;
+
+  primary_output = XRRGetOutputPrimary (screen->display->xdisplay, screen->xroot);
+
+  res = None;
+  for (i = 0; i < crtc->noutput; i++)
+    {
+      output = XRRGetOutputInfo (screen->display->xdisplay, resources, crtc->outputs[i]);
+      if (output->connection != RR_Disconnected &&
+          (res == None || crtc->outputs[i] == primary_output))
+        res = crtc->outputs[i];
+      XRRFreeOutputInfo (output);
+    }
+
+  return res;
+}
+
+#endif
+
 static void
 reload_monitor_infos (MetaScreen *screen)
 {
@@ -406,10 +459,9 @@ reload_monitor_infos (MetaScreen *screen)
   }
 
   display = screen->display;
-  
-  if (screen->monitor_infos)
-    g_free (screen->monitor_infos);
-  
+
+  /* Any previous screen->monitor_infos is freed by the caller */
+
   screen->monitor_infos = NULL;
   screen->n_monitor_infos = 0;
   screen->last_monitor_index = 0;
@@ -433,7 +485,7 @@ reload_monitor_infos (MetaScreen *screen)
       meta_topic (META_DEBUG_XINERAMA,
                   "Pretending a single monitor has two Xinerama screens\n");
 
-      screen->monitor_infos = g_new (MetaMonitorInfo, 2);
+      screen->monitor_infos = g_new0 (MetaMonitorInfo, 2);
       screen->n_monitor_infos = 2;
 
       screen->monitor_infos[0].number = 0;
@@ -445,7 +497,7 @@ reload_monitor_infos (MetaScreen *screen)
       screen->monitor_infos[1].rect.x = screen->rect.width / 2;
       screen->monitor_infos[1].rect.width = screen->rect.width / 2;
     }
-  
+
 #ifdef HAVE_XFREE_XINERAMA
   if (screen->n_monitor_infos == 0 &&
       XineramaIsActive (display->xdisplay))
@@ -463,7 +515,7 @@ reload_monitor_infos (MetaScreen *screen)
 
       if (n_infos > 0)
         {
-          screen->monitor_infos = g_new (MetaMonitorInfo, n_infos);
+          screen->monitor_infos = g_new0 (MetaMonitorInfo, n_infos);
           screen->n_monitor_infos = n_infos;
           
           i = 0;
@@ -488,6 +540,30 @@ reload_monitor_infos (MetaScreen *screen)
         }
       
       meta_XFree (infos);
+
+#ifdef HAVE_RANDR
+      {
+        XRRScreenResources *resources;
+
+        resources = XRRGetScreenResourcesCurrent (display->xdisplay, screen->xroot);
+        if (resources)
+          {
+            for (i = 0; i < resources->ncrtc; i++)
+              {
+                XRRCrtcInfo *crtc;
+                MetaMonitorInfo *info;
+
+                crtc = XRRGetCrtcInfo (display->xdisplay, resources, resources->crtcs[i]);
+                info = find_monitor_with_rect (screen, crtc->x, crtc->y, (int)crtc->width, (int)crtc->height);
+                if (info)
+                  info->output = find_main_output_for_crtc (screen, resources, crtc);
+
+                XRRFreeCrtcInfo (crtc);
+              }
+            XRRFreeScreenResources (resources);
+          }
+      }
+#endif
     }
   else if (screen->n_monitor_infos > 0)
     {
@@ -524,7 +600,7 @@ reload_monitor_infos (MetaScreen *screen)
 	{
           g_assert (n_monitors > 0);
           
-          screen->monitor_infos = g_new (MetaMonitorInfo, n_monitors);
+          screen->monitor_infos = g_new0 (MetaMonitorInfo, n_monitors);
           screen->n_monitor_infos = n_monitors;
           
           i = 0;
@@ -568,7 +644,7 @@ reload_monitor_infos (MetaScreen *screen)
       meta_topic (META_DEBUG_XINERAMA,
                   "No Xinerama screens, using default screen info\n");
           
-      screen->monitor_infos = g_new (MetaMonitorInfo, 1);
+      screen->monitor_infos = g_new0 (MetaMonitorInfo, 1);
       screen->n_monitor_infos = 1;
           
       screen->monitor_infos[0].number = 0;
@@ -577,6 +653,8 @@ reload_monitor_infos (MetaScreen *screen)
 
   filter_mirrored_monitors (screen);
 
+  screen->monitor_infos[screen->primary_monitor_index].is_primary = TRUE;
+
   g_assert (screen->n_monitor_infos > 0);
   g_assert (screen->monitor_infos != NULL);
 }
@@ -2882,28 +2960,17 @@ meta_screen_resize (MetaScreen *screen,
                     int         height)
 {
   GSList *windows, *tmp;
+  MetaMonitorInfo *old_monitor_infos;
 
   screen->rect.width = width;
   screen->rect.height = height;
 
-  /* Clear monitor for all windows on this screen, as it will become
-   * invalid. */
-  windows = meta_display_list_windows (screen->display,
-                                       META_LIST_INCLUDE_OVERRIDE_REDIRECT);
-  for (tmp = windows; tmp != NULL; tmp = tmp->next)
-    {
-      MetaWindow *window = tmp->data;
-
-      if (window->screen == screen)
-        {
-          g_signal_emit_by_name (screen, "window-left-monitor", window->monitor->number, window);
-          window->monitor = NULL;
-        }
-    }
+  /* Save the old monitor infos, so they stay valid during the update */
+  old_monitor_infos = screen->monitor_infos;
 
   reload_monitor_infos (screen);
   set_desktop_geometry_hint (screen);
-  
+
   if (screen->display->compositor)
     meta_compositor_sync_screen_size (screen->display->compositor,
 				      screen, width, height);
@@ -2919,9 +2986,10 @@ meta_screen_resize (MetaScreen *screen,
       MetaWindow *window = tmp->data;
 
       if (window->screen == screen)
-        meta_window_update_monitor (window);
+        meta_window_update_for_monitors_changed (window);
     }
 
+  g_free (old_monitor_infos);
   g_slist_free (windows);
 
   g_signal_emit (screen, screen_signals[MONITORS_CHANGED], 0, index);
diff --git a/src/core/window-private.h b/src/core/window-private.h
index 1f6c85e..1d1ae91 100644
--- a/src/core/window-private.h
+++ b/src/core/window-private.h
@@ -640,7 +640,7 @@ void meta_window_update_icon_now (MetaWindow *window);
 
 void meta_window_update_role (MetaWindow *window);
 void meta_window_update_net_wm_type (MetaWindow *window);
-void meta_window_update_monitor (MetaWindow *window);
+void meta_window_update_for_monitors_changed (MetaWindow *window);
 void meta_window_update_on_all_workspaces (MetaWindow *window);
 
 void meta_window_propagate_focus_appearance (MetaWindow *window,
diff --git a/src/core/window.c b/src/core/window.c
index 510f39c..57761ac 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -125,6 +125,9 @@ static gboolean queue_calc_showing_func (MetaWindow *window,
 
 static void meta_window_apply_session_info (MetaWindow                  *window,
                                             const MetaWindowSessionInfo *info);
+static void meta_window_move_between_rects (MetaWindow          *window,
+                                            const MetaRectangle *old_area,
+                                            const MetaRectangle *new_area);
 
 static void unmaximize_window_before_freeing (MetaWindow        *window);
 static void unminimize_window_and_all_transient_parents (MetaWindow *window);
@@ -3521,7 +3524,7 @@ meta_window_is_fullscreen (MetaWindow *window)
 gboolean
 meta_window_is_on_primary_monitor (MetaWindow *window)
 {
-  return window->monitor->number == window->screen->primary_monitor_index;
+  return window->monitor->is_primary;
 }
 
 void
@@ -4328,7 +4331,44 @@ meta_window_get_monitor (MetaWindow *window)
   return window->monitor->number;
 }
 
+/* This is called when the monitor setup has changed. The window->monitor
+ * reference is still "valid", but refer to the previous monitor setup */
 void
+meta_window_update_for_monitors_changed (MetaWindow *window)
+{
+  const MetaMonitorInfo *old, *new;
+  int i;
+
+  old = window->monitor;
+
+  /* Start on primary */
+  new = &window->screen->monitor_infos[window->screen->primary_monitor_index];
+
+  /* But, if we can find the old output on a new monitor, use that */
+  for (i = 0; i < window->screen->n_monitor_infos; i++)
+    {
+      MetaMonitorInfo *info = &window->screen->monitor_infos[i];
+
+      if (info->output == old->output)
+        {
+          new = info;
+          break;
+        }
+    }
+
+  /* This will eventually reach meta_window_update_monitor that
+   * will send leave/enter-monitor events. The old != new monitor
+   * check will always fail (due to the new monitor_infos set) so
+   * we will always send the events, even if the new and old monitor
+   * index is the same. That is right, since the enumeration of the
+   * monitors changed and the same index could be refereing
+   * to a different monitor. */
+  meta_window_move_between_rects (window,
+                                  &old->rect,
+                                  &new->rect);
+}
+
+static void
 meta_window_update_monitor (MetaWindow *window)
 {
   const MetaMonitorInfo *old;
@@ -4347,13 +4387,14 @@ meta_window_update_monitor (MetaWindow *window)
        * workspace is when dropping the window on some other workspace thumbnail directly.
        * That should be handled by explicitly moving the window before changing the
        * workspace
-       * Don't do this if old == NULL, because thats what happens when we're updating
-       * the monitors due to a monitors change event, and we don't want to move
-       * everything to the currently active workspace.
+       * Don't do this if old == NULL, because thats what happens when starting up, and
+       * we don't want to move all windows around from a previous WM instance. Nor do
+       * we want it when moving from one primary monitor to another (can happen during
+       * screen reconfiguration.
        */
       if (meta_prefs_get_workspaces_only_on_primary () &&
           meta_window_is_on_primary_monitor (window)  &&
-          old != NULL &&
+          old != NULL && !old->is_primary &&
           window->screen->active_workspace != window->workspace)
         meta_window_change_workspace (window, window->screen->active_workspace);
 
@@ -4924,6 +4965,31 @@ meta_window_move_frame (MetaWindow  *window,
   meta_window_move (window, user_op, x, y);
 }
 
+static void
+meta_window_move_between_rects (MetaWindow  *window,
+                                const MetaRectangle *old_area,
+                                const MetaRectangle *new_area)
+{
+  int rel_x, rel_y;
+  double scale_x, scale_y;
+
+  rel_x = window->user_rect.x - old_area->x;
+  rel_y = window->user_rect.y - old_area->y;
+  scale_x = (double)new_area->width / old_area->width;
+  scale_y = (double)new_area->height / old_area->height;
+
+  window->user_rect.x = new_area->x + rel_x * scale_x;
+  window->user_rect.y = new_area->y + rel_y * scale_y;
+  window->saved_rect.x = window->user_rect.x;
+  window->saved_rect.y = window->user_rect.y;
+
+  meta_window_move_resize (window, FALSE,
+                           window->user_rect.x,
+                           window->user_rect.y,
+                           window->user_rect.width,
+                           window->user_rect.height);
+}
+
 /**
  * meta_window_move_to_monitor:
  * @window: a #MetaWindow
@@ -4937,8 +5003,6 @@ meta_window_move_to_monitor (MetaWindow  *window,
                              int          monitor)
 {
   MetaRectangle old_area, new_area;
-  int rel_x, rel_y;
-  double scale_x, scale_y;
 
   if (monitor == window->monitor->number)
     return;
@@ -4950,21 +5014,7 @@ meta_window_move_to_monitor (MetaWindow  *window,
                                          monitor,
                                          &new_area);
 
-  rel_x = window->user_rect.x - old_area.x;
-  rel_y = window->user_rect.y - old_area.y;
-  scale_x = (double)new_area.width / old_area.width;
-  scale_y = (double)new_area.height / old_area.height;
-
-  window->user_rect.x = new_area.x + rel_x * scale_x;
-  window->user_rect.y = new_area.y + rel_y * scale_y;
-  window->saved_rect.x = window->user_rect.x;
-  window->saved_rect.y = window->user_rect.y;
-
-  meta_window_move_resize (window, FALSE,
-                           window->user_rect.x,
-                           window->user_rect.y,
-                           window->user_rect.width,
-                           window->user_rect.height);
+  meta_window_move_between_rects (window, &old_area, &new_area);
 }
 
 void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]