c7bdda5
diff --git a/plugins/xrandr/gsd-xrandr-manager.c b/plugins/xrandr/gsd-xrandr-manager.c
e60c9bb
index 4d8ce2f..4057962 100644
c7bdda5
--- a/plugins/xrandr/gsd-xrandr-manager.c
c7bdda5
+++ b/plugins/xrandr/gsd-xrandr-manager.c
c7bdda5
@@ -82,6 +82,10 @@ struct GsdXrandrManagerPrivate
aeb799d
         GnomeRRLabeler *labeler;
aeb799d
         GConfClient *client;
aeb799d
         int notify_id;
aeb799d
+
aeb799d
+        /* fn-F7 status */
aeb799d
+        int             current_fn_f7_config;             /* -1 if no configs */
aeb799d
+        GnomeRRConfig **fn_f7_configs;  /* NULL terminated, NULL if there are no configs */
aeb799d
 };
aeb799d
 
aeb799d
 static void     gsd_xrandr_manager_class_init  (GsdXrandrManagerClass *klass);
e60c9bb
@@ -127,6 +131,421 @@ on_client_message (GdkXEvent  *xevent,
aeb799d
         return GDK_FILTER_CONTINUE;
aeb799d
 }
aeb799d
 
aeb799d
+static gboolean
aeb799d
+is_laptop (GnomeOutputInfo *output)
aeb799d
+{
aeb799d
+        const char *output_name = output->name;
aeb799d
+
aeb799d
+        if (output->connected && output_name &&
aeb799d
+            (strstr ("lvds", output_name)	||
aeb799d
+             strstr ("LVDS", output_name)	||
aeb799d
+             strstr ("Lvds", output_name)))
aeb799d
+        {
aeb799d
+                return TRUE;
aeb799d
+        }
aeb799d
+
aeb799d
+        return FALSE;
aeb799d
+}
aeb799d
+
aeb799d
+static gboolean
aeb799d
+get_clone_size (GnomeRRScreen *screen, int *width, int *height)
aeb799d
+{
aeb799d
+        GnomeRRMode **modes = gnome_rr_screen_list_clone_modes (screen);
aeb799d
+        int best_w, best_h;
aeb799d
+        int i;
aeb799d
+
aeb799d
+        best_w = 0;
aeb799d
+        best_h = 0;
aeb799d
+
aeb799d
+        for (i = 0; modes[i] != NULL; ++i) {
aeb799d
+                GnomeRRMode *mode = modes[i];
aeb799d
+                int w, h;
aeb799d
+
aeb799d
+                w = gnome_rr_mode_get_width (mode);
aeb799d
+                h = gnome_rr_mode_get_height (mode);
aeb799d
+
aeb799d
+                if (w * h > best_w * best_h) {
aeb799d
+                        best_w = w;
aeb799d
+                        best_h = h;
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        if (best_w > 0 && best_h > 0) {
aeb799d
+                if (width)
aeb799d
+                        *width = best_w;
aeb799d
+                if (height)
aeb799d
+                        *height = best_h;
aeb799d
+
aeb799d
+                return TRUE;
aeb799d
+        }
aeb799d
+
aeb799d
+        return FALSE;
aeb799d
+}
aeb799d
+
aeb799d
+static void
aeb799d
+print_output (GnomeOutputInfo *info)
aeb799d
+{
aeb799d
+        g_print ("  Output: %s attached to %s\n", info->display_name, info->name);
aeb799d
+        g_print ("     status: %s\n", info->on ? "on" : "off");
aeb799d
+        g_print ("     width: %d\n", info->width);
aeb799d
+        g_print ("     height: %d\n", info->height);
aeb799d
+        g_print ("     rate: %d\n", info->rate);
aeb799d
+        g_print ("     position: %d %d\n", info->x, info->y);
aeb799d
+}
aeb799d
+
aeb799d
+static void
aeb799d
+print_configuration (GnomeRRConfig *config, const char *header)
aeb799d
+{
aeb799d
+        int i;
aeb799d
+
aeb799d
+        g_print ("=== %s Configuration ===\n", header);
aeb799d
+        if (!config) {
aeb799d
+                g_print ("  none\n");
aeb799d
+                return;
aeb799d
+        }
aeb799d
+        
aeb799d
+        for (i = 0; config->outputs[i] != NULL; ++i)
aeb799d
+                print_output (config->outputs[i]);
aeb799d
+}
aeb799d
+
aeb799d
+static GnomeRRConfig *
aeb799d
+make_clone_setup (GnomeRRScreen *screen)
aeb799d
+{
aeb799d
+        GnomeRRConfig *result;
aeb799d
+        int width, height;
aeb799d
+        int i;
aeb799d
+
aeb799d
+        if (!get_clone_size (screen, &width, &height))
aeb799d
+                return NULL;
aeb799d
+        
aeb799d
+        result = gnome_rr_config_new_current (screen);
aeb799d
+
aeb799d
+        for (i = 0; result->outputs[i] != NULL; ++i) {
aeb799d
+                GnomeOutputInfo *info = result->outputs[i];
aeb799d
+
aeb799d
+                info->on = FALSE;
aeb799d
+                if (info->connected) {
aeb799d
+                        GnomeRROutput *output =
aeb799d
+                                gnome_rr_screen_get_output_by_name (screen, info->name);
aeb799d
+                        GnomeRRMode **modes = gnome_rr_output_list_modes (output);
aeb799d
+                        int j;
aeb799d
+                        int best_rate = 0;
aeb799d
+                        
aeb799d
+                        for (j = 0; modes[j] != NULL; ++j) {
aeb799d
+                                GnomeRRMode *mode = modes[j];
aeb799d
+                                int w, h;
aeb799d
+                                
aeb799d
+                                w = gnome_rr_mode_get_width (mode);
aeb799d
+                                h = gnome_rr_mode_get_height (mode);
aeb799d
+                                
aeb799d
+                                if (w == width && h == height) {
aeb799d
+                                        int r = gnome_rr_mode_get_freq (mode);
aeb799d
+                                        if (r > best_rate)
aeb799d
+                                                best_rate = r;
aeb799d
+                                }
aeb799d
+                        }
aeb799d
+
aeb799d
+                        if (best_rate > 0) {
aeb799d
+                                info->on = TRUE;
aeb799d
+                                info->width = width;
aeb799d
+                                info->height = height;
aeb799d
+                                info->rate = best_rate;
e60c9bb
+                                info->rotation = GNOME_RR_ROTATION_0;
aeb799d
+                                info->x = 0;
aeb799d
+                                info->y = 0;
aeb799d
+                        }
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        print_configuration (result, "clone setup");
aeb799d
+        
aeb799d
+        return result;
aeb799d
+}
aeb799d
+
aeb799d
+static gboolean
aeb799d
+turn_on (GnomeRRScreen *screen,
aeb799d
+         GnomeOutputInfo *info,
aeb799d
+         int x, int y)
aeb799d
+{
aeb799d
+        GnomeRROutput *output =
aeb799d
+                gnome_rr_screen_get_output_by_name (screen, info->name);
aeb799d
+        GnomeRRMode *mode = gnome_rr_output_get_preferred_mode (output);
aeb799d
+
aeb799d
+        if (mode) {
aeb799d
+                info->on = TRUE;
aeb799d
+                info->x = x;
aeb799d
+                info->y = y;
aeb799d
+                info->width = gnome_rr_mode_get_width (mode);
aeb799d
+                info->height = gnome_rr_mode_get_height (mode);
aeb799d
+                info->rotation = GNOME_RR_ROTATION_0;
aeb799d
+                info->rate = gnome_rr_mode_get_freq (mode);
aeb799d
+
aeb799d
+                return TRUE;
aeb799d
+        }
aeb799d
+
aeb799d
+        return FALSE;
aeb799d
+}
aeb799d
+
aeb799d
+static GnomeRRConfig *
aeb799d
+make_laptop_setup (GnomeRRScreen *screen)
aeb799d
+{
aeb799d
+        /* Turn on the laptop, disable everything else */
aeb799d
+        GnomeRRConfig *result = gnome_rr_config_new_current (screen);
aeb799d
+        int i;
aeb799d
+
aeb799d
+        for (i = 0; result->outputs[i] != NULL; ++i) {
aeb799d
+                GnomeOutputInfo *info = result->outputs[i];
aeb799d
+
aeb799d
+                if (is_laptop (info)) {
aeb799d
+                        if (!info->on) {
aeb799d
+                                if (!turn_on (screen, info, 0, 0)) {
aeb799d
+                                        gnome_rr_config_free (result);
aeb799d
+                                        result = NULL;
aeb799d
+                                        break;
aeb799d
+                                }
aeb799d
+                        }
aeb799d
+                }
aeb799d
+                else {
aeb799d
+                        info->on = FALSE;
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        print_configuration (result, "Laptop setup");
aeb799d
+        
aeb799d
+        /* FIXME - Maybe we should return NULL if there is more than
aeb799d
+         * one connected "laptop" screen?
aeb799d
+         */
aeb799d
+        return result;
aeb799d
+        
aeb799d
+}
aeb799d
+
aeb799d
+static GnomeRRConfig *
aeb799d
+make_xinerama_setup (GnomeRRScreen *screen)
aeb799d
+{
aeb799d
+        /* Turn on everything that has a preferred mode, and
aeb799d
+         * position it from left to right
aeb799d
+         */
aeb799d
+        GnomeRRConfig *result = gnome_rr_config_new_current (screen);
aeb799d
+        int i;
aeb799d
+        int x;
aeb799d
+
aeb799d
+        x = 0;
aeb799d
+        for (i = 0; result->outputs[i] != NULL; ++i) {
aeb799d
+                GnomeOutputInfo *info = result->outputs[i];
aeb799d
+
aeb799d
+                if (is_laptop (info)) {
e60c9bb
+                        if (info->on || turn_on (screen, info, x, 0)) {
e60c9bb
+                                x += info->width;
aeb799d
+                        }
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        for (i = 0; result->outputs[i] != NULL; ++i) {
aeb799d
+                GnomeOutputInfo *info = result->outputs[i];
aeb799d
+
aeb799d
+                if (info->connected && !is_laptop (info)) {
e60c9bb
+                        if (info->on || turn_on (screen, info, x, 0)) {
e60c9bb
+                                x += info->width;
aeb799d
+                        }
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        print_configuration (result, "xinerama setup");
aeb799d
+        
aeb799d
+        return result;
aeb799d
+}
aeb799d
+
aeb799d
+static GnomeRRConfig *
aeb799d
+make_other_setup (GnomeRRScreen *screen)
aeb799d
+{
aeb799d
+        /* Turn off all laptops, and make all external monitors clone
aeb799d
+         * from (0, 0)
aeb799d
+         */
aeb799d
+        
aeb799d
+        GnomeRRConfig *result = gnome_rr_config_new_current (screen);
aeb799d
+        int i;
aeb799d
+
aeb799d
+        for (i = 0; result->outputs[i] != NULL; ++i) {
aeb799d
+                GnomeOutputInfo *info = result->outputs[i];
aeb799d
+
aeb799d
+                if (is_laptop (info)) {
aeb799d
+                        info->on = FALSE;
aeb799d
+                }
aeb799d
+                else {
aeb799d
+                        if (info->connected && !info->on) {
aeb799d
+                                turn_on (screen, info, 0, 0);
aeb799d
+                        }
aeb799d
+               }
aeb799d
+        }
aeb799d
+
aeb799d
+        print_configuration (result, "other setup");
aeb799d
+        
aeb799d
+        return result;
aeb799d
+}
aeb799d
+
aeb799d
+static GPtrArray *
aeb799d
+sanitize (GPtrArray *array)
aeb799d
+{
aeb799d
+        int i;
aeb799d
+        GPtrArray *new;
aeb799d
+
aeb799d
+        g_print ("before sanitizing\n");
aeb799d
+
aeb799d
+        for (i = 0; i < array->len; ++i) {
aeb799d
+                if (array->pdata[i]) {
aeb799d
+                        print_configuration (array->pdata[i], "before");
aeb799d
+                }
aeb799d
+        }
aeb799d
+        
aeb799d
+
aeb799d
+        /* Remove configurations that are duplicates of
aeb799d
+         * configurations earlier in the cycle
aeb799d
+         */
aeb799d
+        for (i = 0; i < array->len; ++i) {
aeb799d
+                int j;
aeb799d
+
aeb799d
+                for (j = 0; j < i; ++j) {
aeb799d
+                        GnomeRRConfig *this = array->pdata[j];
aeb799d
+                        GnomeRRConfig *other = array->pdata[i];
aeb799d
+
aeb799d
+                        if (this && other && gnome_rr_config_equal (this, other)) {
aeb799d
+                                g_print ("removing duplicate configuration\n");
aeb799d
+                                gnome_rr_config_free (this);
aeb799d
+                                array->pdata[j] = NULL;
aeb799d
+                                break;
aeb799d
+                        }
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        for (i = 0; i < array->len; ++i) {
aeb799d
+                GnomeRRConfig *config = array->pdata[i];
aeb799d
+
aeb799d
+                if (config) {
aeb799d
+                        gboolean all_off = TRUE;
aeb799d
+                        int j;
aeb799d
+                        
aeb799d
+                        for (j = 0; config->outputs[j] != NULL; ++j) {
aeb799d
+                                if (config->outputs[j]->on)
aeb799d
+                                        all_off = FALSE;
aeb799d
+                        }
aeb799d
+                        
aeb799d
+                        if (all_off) {
aeb799d
+                                gnome_rr_config_free (array->pdata[i]);
aeb799d
+                                array->pdata[i] = NULL;
aeb799d
+                        }
aeb799d
+                }
aeb799d
+        }
aeb799d
+        
aeb799d
+        /* Remove NULL configurations */
aeb799d
+        new = g_ptr_array_new ();
aeb799d
+
aeb799d
+        for (i = 0; i < array->len; ++i) {
aeb799d
+                if (array->pdata[i]) {
aeb799d
+                        g_ptr_array_add (new, array->pdata[i]);
aeb799d
+                        print_configuration (array->pdata[i], "Final");
aeb799d
+                }
aeb799d
+        }
aeb799d
+
aeb799d
+        g_ptr_array_add (new, NULL);
aeb799d
+
aeb799d
+        g_ptr_array_free (array, TRUE);
aeb799d
+
aeb799d
+        return new;
aeb799d
+}
aeb799d
+
aeb799d
+static void
aeb799d
+generate_fn_f7_configs (GsdXrandrManager *mgr)
aeb799d
+{
aeb799d
+        GPtrArray *array = g_ptr_array_new ();
aeb799d
+        GnomeRRScreen *screen = mgr->priv->rw_screen;
aeb799d
+
aeb799d
+        g_print ("Generating configurations\n");
aeb799d
+        
aeb799d
+        /* Free any existing list of configurations */
aeb799d
+        if (mgr->priv->fn_f7_configs) {
aeb799d
+                int i;
aeb799d
+
aeb799d
+                for (i = 0; mgr->priv->fn_f7_configs[i] != NULL; ++i)
aeb799d
+                        gnome_rr_config_free (mgr->priv->fn_f7_configs[i]);
aeb799d
+                g_free (mgr->priv->fn_f7_configs);
aeb799d
+
aeb799d
+                mgr->priv->fn_f7_configs = NULL;
aeb799d
+                mgr->priv->current_fn_f7_config = -1;
aeb799d
+        }
aeb799d
+
aeb799d
+        g_ptr_array_add (array, gnome_rr_config_new_current (screen));
aeb799d
+        g_ptr_array_add (array, make_xinerama_setup (screen));
aeb799d
+        g_ptr_array_add (array, make_clone_setup (screen));
aeb799d
+        g_ptr_array_add (array, make_laptop_setup (screen));
aeb799d
+        g_ptr_array_add (array, make_other_setup (screen));
aeb799d
+        g_ptr_array_add (array, gnome_rr_config_new_stored (screen));
aeb799d
+        g_ptr_array_add (array, NULL);
aeb799d
+
aeb799d
+        array = sanitize (array);
aeb799d
+        
aeb799d
+        mgr->priv->fn_f7_configs = (GnomeRRConfig **)g_ptr_array_free (array, FALSE);
aeb799d
+        mgr->priv->current_fn_f7_config = 0;
aeb799d
+}
aeb799d
+
aeb799d
+static void
aeb799d
+handle_fn_f7 (GsdXrandrManager *mgr)
aeb799d
+{
aeb799d
+        GsdXrandrManagerPrivate *priv = mgr->priv;
aeb799d
+        GnomeRRScreen *screen = priv->rw_screen;
aeb799d
+        GnomeRRConfig *current;
aeb799d
+        
aeb799d
+        /* Theory of fn-F7 operation
aeb799d
+         *
aeb799d
+         * We maintain a datastructure "fn_f7_status", that contains
aeb799d
+         * a list of GnomeRRConfig's. Each of the GnomeRRConfigs has a
aeb799d
+         * mode (or "off") for each connected output.
aeb799d
+         *
aeb799d
+         * When the user hits fn-F7, we cycle to the next GnomeRRConfig
aeb799d
+         * in the data structure. If the data structure does not exist, it
aeb799d
+         * is generated. If the configs in the data structure do not match
aeb799d
+         * the current hardware reality, it is regenerated.
aeb799d
+         *
aeb799d
+         */
aeb799d
+        g_print ("Handling fn-f7\n");
aeb799d
+        gnome_rr_screen_refresh (screen);
aeb799d
+
aeb799d
+        if (!priv->fn_f7_configs)
aeb799d
+                generate_fn_f7_configs (mgr);
aeb799d
+
aeb799d
+        current = gnome_rr_config_new_current (screen);
aeb799d
+        
e60c9bb
+        if (priv->fn_f7_configs && 
e60c9bb
+            (!gnome_rr_config_match (current, priv->fn_f7_configs[0]) ||
e60c9bb
+             !gnome_rr_config_equal (current, priv->fn_f7_configs[mgr->priv->current_fn_f7_config]))) {
e60c9bb
+                    /* Our view of the world is incorrect, so regenerate the
e60c9bb
+                     * configurations
e60c9bb
+                     */
e60c9bb
+                    generate_fn_f7_configs (mgr);
e60c9bb
+            }
aeb799d
+
aeb799d
+        gnome_rr_config_free (current);
aeb799d
+        
aeb799d
+        if (priv->fn_f7_configs) {
aeb799d
+                mgr->priv->current_fn_f7_config++;
aeb799d
+
aeb799d
+                if (priv->fn_f7_configs[mgr->priv->current_fn_f7_config] == NULL)
aeb799d
+                        mgr->priv->current_fn_f7_config = 0;
aeb799d
+
aeb799d
+                g_print ("cycling to next configuration (%d)\n", mgr->priv->current_fn_f7_config);
aeb799d
+
aeb799d
+                print_configuration (priv->fn_f7_configs[mgr->priv->current_fn_f7_config], "new config");
aeb799d
+
aeb799d
+                g_print ("applying\n");
aeb799d
+                
aeb799d
+                gnome_rr_config_apply (priv->fn_f7_configs[mgr->priv->current_fn_f7_config],
aeb799d
+                                       screen);
aeb799d
+        }
aeb799d
+        else {
aeb799d
+                g_print ("no configurations generated\n");
aeb799d
+        }
aeb799d
+        g_print ("done handling fn-f7\n");
aeb799d
+}
aeb799d
+
aeb799d
 static GdkFilterReturn
aeb799d
 event_filter (GdkXEvent           *xevent,
aeb799d
               GdkEvent            *event,
e60c9bb
@@ -142,12 +561,9 @@ event_filter (GdkXEvent           *xevent,
aeb799d
         if (xev->xany.type != KeyPress && xev->xany.type != KeyRelease)
aeb799d
                 return GDK_FILTER_CONTINUE;
aeb799d
 
aeb799d
-        if (xev->xkey.keycode == manager->priv->keycode) {
aeb799d
-                /* FIXME: here we should cycle between valid
aeb799d
-                 * configurations, and save them
aeb799d
-                 */
aeb799d
-                gnome_rr_config_apply_stored (manager->priv->rw_screen);
aeb799d
-
aeb799d
+        if (xev->xkey.keycode == manager->priv->keycode && xev->xany.type == KeyPress) {
aeb799d
+                handle_fn_f7 (manager);
aeb799d
+                
aeb799d
                 return GDK_FILTER_CONTINUE;
aeb799d
         }
aeb799d
 
e60c9bb
@@ -869,6 +1285,9 @@ gsd_xrandr_manager_init (GsdXrandrManager *manager)
aeb799d
         manager->priv = GSD_XRANDR_MANAGER_GET_PRIVATE (manager);
aeb799d
 
aeb799d
         manager->priv->keycode = keycode;
aeb799d
+
aeb799d
+        manager->priv->current_fn_f7_config = -1;
aeb799d
+        manager->priv->fn_f7_configs = NULL;
aeb799d
 }
aeb799d
 
aeb799d
 static void