summaryrefslogtreecommitdiff
path: root/plugins/xrandr/msd-xrandr-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/xrandr/msd-xrandr-manager.c')
-rw-r--r--plugins/xrandr/msd-xrandr-manager.c2584
1 files changed, 2584 insertions, 0 deletions
diff --git a/plugins/xrandr/msd-xrandr-manager.c b/plugins/xrandr/msd-xrandr-manager.c
new file mode 100644
index 0000000..dc00be1
--- /dev/null
+++ b/plugins/xrandr/msd-xrandr-manager.c
@@ -0,0 +1,2584 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <[email protected]>
+ * Copyright (C) 2007, 2008 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <mateconf/mateconf-client.h>
+#include <dbus/dbus-glib.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include <libmateui/mate-rr-config.h>
+#include <libmateui/mate-rr.h>
+#include <libmateui/mate-rr-labeler.h>
+
+#ifdef HAVE_LIBMATENOTIFY
+#include <libmatenotify/notify.h>
+#endif
+
+#include "mate-settings-profile.h"
+#include "msd-xrandr-manager.h"
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 255
+#endif
+
+#define MSD_XRANDR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MSD_TYPE_XRANDR_MANAGER, MsdXrandrManagerPrivate))
+
+#define CONF_DIR "/apps/mate_settings_daemon/xrandr"
+#define CONF_KEY_SHOW_NOTIFICATION_ICON (CONF_DIR "/show_notification_icon")
+#define CONF_KEY_TURN_ON_EXTERNAL_MONITORS_AT_STARTUP (CONF_DIR "/turn_on_external_monitors_at_startup")
+#define CONF_KEY_TURN_ON_LAPTOP_MONITOR_AT_STARTUP (CONF_DIR "/turn_on_laptop_monitor_at_startup")
+#define CONF_KEY_DEFAULT_CONFIGURATION_FILE (CONF_DIR "/default_configuration_file")
+
+#define VIDEO_KEYSYM "XF86Display"
+#define ROTATE_KEYSYM "XF86RotateWindows"
+
+/* Number of seconds that the confirmation dialog will last before it resets the
+ * RANDR configuration to its old state.
+ */
+#define CONFIRMATION_DIALOG_SECONDS 30
+
+/* name of the icon files (msd-xrandr.svg, etc.) */
+#define MSD_XRANDR_ICON_NAME "msd-xrandr"
+
+/* executable of the control center's display configuration capplet */
+#define MSD_XRANDR_DISPLAY_CAPPLET "mate-control-center display"
+
+#define MSD_DBUS_PATH "/org/mate/SettingsDaemon"
+#define MSD_DBUS_NAME "org.mate.SettingsDaemon"
+#define MSD_XRANDR_DBUS_PATH MSD_DBUS_PATH "/XRANDR"
+#define MSD_XRANDR_DBUS_NAME MSD_DBUS_NAME ".XRANDR"
+
+struct MsdXrandrManagerPrivate
+{
+ DBusGConnection *dbus_connection;
+
+ /* Key code of the XF86Display key (Fn-F7 on Thinkpads, Fn-F4 on HP machines, etc.) */
+ guint switch_video_mode_keycode;
+
+ /* Key code of the XF86RotateWindows key (present on some tablets) */
+ guint rotate_windows_keycode;
+
+ MateRRScreen *rw_screen;
+ gboolean running;
+
+ GtkStatusIcon *status_icon;
+ GtkWidget *popup_menu;
+ MateRRConfig *configuration;
+ MateRRLabeler *labeler;
+ MateConfClient *client;
+ int notify_id;
+
+ /* fn-F7 status */
+ int current_fn_f7_config; /* -1 if no configs */
+ MateRRConfig **fn_f7_configs; /* NULL terminated, NULL if there are no configs */
+
+ /* Last time at which we got a "screen got reconfigured" event; see on_randr_event() */
+ guint32 last_config_timestamp;
+};
+
+static const MateRRRotation possible_rotations[] = {
+ MATE_RR_ROTATION_0,
+ MATE_RR_ROTATION_90,
+ MATE_RR_ROTATION_180,
+ MATE_RR_ROTATION_270
+ /* We don't allow REFLECT_X or REFLECT_Y for now, as mate-display-properties doesn't allow them, either */
+};
+
+static void msd_xrandr_manager_class_init (MsdXrandrManagerClass *klass);
+static void msd_xrandr_manager_init (MsdXrandrManager *xrandr_manager);
+static void msd_xrandr_manager_finalize (GObject *object);
+
+static void error_message (MsdXrandrManager *mgr, const char *primary_text, GError *error_to_display, const char *secondary_text);
+
+static void status_icon_popup_menu (MsdXrandrManager *manager, guint button, guint32 timestamp);
+static void run_display_capplet (GtkWidget *widget);
+static void get_allowed_rotations_for_output (MateRRConfig *config,
+ MateRRScreen *rr_screen,
+ MateOutputInfo *output,
+ int *out_num_rotations,
+ MateRRRotation *out_rotations);
+
+G_DEFINE_TYPE (MsdXrandrManager, msd_xrandr_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static FILE *log_file;
+
+static void
+log_open (void)
+{
+ char *toggle_filename;
+ char *log_filename;
+ struct stat st;
+
+ if (log_file)
+ return;
+
+ toggle_filename = g_build_filename (g_get_home_dir (), "msd-debug-randr", NULL);
+ log_filename = g_build_filename (g_get_home_dir (), "msd-debug-randr.log", NULL);
+
+ if (stat (toggle_filename, &st) != 0)
+ goto out;
+
+ log_file = fopen (log_filename, "a");
+
+ if (log_file && ftell (log_file) == 0)
+ fprintf (log_file, "To keep this log from being created, please rm ~/msd-debug-randr\n");
+
+out:
+ g_free (toggle_filename);
+ g_free (log_filename);
+}
+
+static void
+log_close (void)
+{
+ if (log_file) {
+ fclose (log_file);
+ log_file = NULL;
+ }
+}
+
+static void
+log_msg (const char *format, ...)
+{
+ if (log_file) {
+ va_list args;
+
+ va_start (args, format);
+ vfprintf (log_file, format, args);
+ va_end (args);
+ }
+}
+
+static void
+log_output (MateOutputInfo *output)
+{
+ log_msg (" %s: ", output->name ? output->name : "unknown");
+
+ if (output->connected) {
+ if (output->on) {
+ log_msg ("%dx%d@%d +%d+%d",
+ output->width,
+ output->height,
+ output->rate,
+ output->x,
+ output->y);
+ } else
+ log_msg ("off");
+ } else
+ log_msg ("disconnected");
+
+ if (output->display_name)
+ log_msg (" (%s)", output->display_name);
+
+ if (output->primary)
+ log_msg (" (primary output)");
+
+ log_msg ("\n");
+}
+
+static void
+log_configuration (MateRRConfig *config)
+{
+ int i;
+
+ log_msg (" cloned: %s\n", config->clone ? "yes" : "no");
+
+ for (i = 0; config->outputs[i] != NULL; i++)
+ log_output (config->outputs[i]);
+
+ if (i == 0)
+ log_msg (" no outputs!\n");
+}
+
+static char
+timestamp_relationship (guint32 a, guint32 b)
+{
+ if (a < b)
+ return '<';
+ else if (a > b)
+ return '>';
+ else
+ return '=';
+}
+
+static void
+log_screen (MateRRScreen *screen)
+{
+ MateRRConfig *config;
+ int min_w, min_h, max_w, max_h;
+ guint32 change_timestamp, config_timestamp;
+
+ if (!log_file)
+ return;
+
+ config = mate_rr_config_new_current (screen);
+
+ mate_rr_screen_get_ranges (screen, &min_w, &max_w, &min_h, &max_h);
+ mate_rr_screen_get_timestamps (screen, &change_timestamp, &config_timestamp);
+
+ log_msg (" Screen min(%d, %d), max(%d, %d), change=%u %c config=%u\n",
+ min_w, min_h,
+ max_w, max_h,
+ change_timestamp,
+ timestamp_relationship (change_timestamp, config_timestamp),
+ config_timestamp);
+
+ log_configuration (config);
+ mate_rr_config_free (config);
+}
+
+static void
+log_configurations (MateRRConfig **configs)
+{
+ int i;
+
+ if (!configs) {
+ log_msg (" No configurations\n");
+ return;
+ }
+
+ for (i = 0; configs[i]; i++) {
+ log_msg (" Configuration %d\n", i);
+ log_configuration (configs[i]);
+ }
+}
+
+static void
+show_timestamps_dialog (MsdXrandrManager *manager, const char *msg)
+{
+#if 1
+ return;
+#else
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ GtkWidget *dialog;
+ guint32 change_timestamp, config_timestamp;
+ static int serial;
+
+ mate_rr_screen_get_timestamps (priv->rw_screen, &change_timestamp, &config_timestamp);
+
+ dialog = gtk_message_dialog_new (NULL,
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ "RANDR timestamps (%d):\n%s\nchange: %u\nconfig: %u",
+ serial++,
+ msg,
+ change_timestamp,
+ config_timestamp);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_widget_show (dialog);
+#endif
+}
+
+/* This function centralizes the use of mate_rr_config_apply_from_filename_with_time().
+ *
+ * Optionally filters out MATE_RR_ERROR_NO_MATCHING_CONFIG from
+ * mate_rr_config_apply_from_filename_with_time(), since that is not usually an error.
+ */
+static gboolean
+apply_configuration_from_filename (MsdXrandrManager *manager,
+ const char *filename,
+ gboolean no_matching_config_is_an_error,
+ guint32 timestamp,
+ GError **error)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ GError *my_error;
+ gboolean success;
+ char *str;
+
+ str = g_strdup_printf ("Applying %s with timestamp %d", filename, timestamp);
+ show_timestamps_dialog (manager, str);
+ g_free (str);
+
+ my_error = NULL;
+ success = mate_rr_config_apply_from_filename_with_time (priv->rw_screen, filename, timestamp, &my_error);
+ if (success)
+ return TRUE;
+
+ if (g_error_matches (my_error, MATE_RR_ERROR, MATE_RR_ERROR_NO_MATCHING_CONFIG)) {
+ if (no_matching_config_is_an_error)
+ goto fail;
+
+ /* This is not an error; the user probably changed his monitors
+ * and so they don't match any of the stored configurations.
+ */
+ g_error_free (my_error);
+ return TRUE;
+ }
+
+fail:
+ g_propagate_error (error, my_error);
+ return FALSE;
+}
+
+/* This function centralizes the use of mate_rr_config_apply_with_time().
+ *
+ * Applies a configuration and displays an error message if an error happens.
+ * We just return whether setting the configuration succeeded.
+ */
+static gboolean
+apply_configuration_and_display_error (MsdXrandrManager *manager, MateRRConfig *config, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = manager->priv;
+ GError *error;
+ gboolean success;
+
+ error = NULL;
+ success = mate_rr_config_apply_with_time (config, priv->rw_screen, timestamp, &error);
+ if (!success) {
+ log_msg ("Could not switch to the following configuration (timestamp %u): %s\n", timestamp, error->message);
+ log_configuration (config);
+ error_message (manager, _("Could not switch the monitor configuration"), error, NULL);
+ g_error_free (error);
+ }
+
+ return success;
+}
+
+static void
+restore_backup_configuration_without_messages (const char *backup_filename, const char *intended_filename)
+{
+ backup_filename = mate_rr_config_get_backup_filename ();
+ rename (backup_filename, intended_filename);
+}
+
+static void
+restore_backup_configuration (MsdXrandrManager *manager, const char *backup_filename, const char *intended_filename, guint32 timestamp)
+{
+ int saved_errno;
+
+ if (rename (backup_filename, intended_filename) == 0) {
+ GError *error;
+
+ error = NULL;
+ if (!apply_configuration_from_filename (manager, intended_filename, FALSE, timestamp, &error)) {
+ error_message (manager, _("Could not restore the display's configuration"), error, NULL);
+
+ if (error)
+ g_error_free (error);
+ }
+
+ return;
+ }
+
+ saved_errno = errno;
+
+ /* ENOENT means the original file didn't exist. That is *not* an error;
+ * the backup was not created because there wasn't even an original
+ * monitors.xml (such as on a first-time login). Note that *here* there
+ * is a "didn't work" monitors.xml, so we must delete that one.
+ */
+ if (saved_errno == ENOENT)
+ unlink (intended_filename);
+ else {
+ char *msg;
+
+ msg = g_strdup_printf ("Could not rename %s to %s: %s",
+ backup_filename, intended_filename,
+ g_strerror (saved_errno));
+ error_message (manager,
+ _("Could not restore the display's configuration from a backup"),
+ NULL,
+ msg);
+ g_free (msg);
+ }
+
+ unlink (backup_filename);
+}
+
+typedef struct {
+ MsdXrandrManager *manager;
+ GtkWidget *dialog;
+
+ int countdown;
+ int response_id;
+} TimeoutDialog;
+
+static void
+print_countdown_text (TimeoutDialog *timeout)
+{
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (timeout->dialog),
+ ngettext ("The display will be reset to its previous configuration in %d second",
+ "The display will be reset to its previous configuration in %d seconds",
+ timeout->countdown),
+ timeout->countdown);
+}
+
+static gboolean
+timeout_cb (gpointer data)
+{
+ TimeoutDialog *timeout = data;
+
+ timeout->countdown--;
+
+ if (timeout->countdown == 0) {
+ timeout->response_id = GTK_RESPONSE_CANCEL;
+ gtk_main_quit ();
+ } else {
+ print_countdown_text (timeout);
+ }
+
+ return TRUE;
+}
+
+static void
+timeout_response_cb (GtkDialog *dialog, int response_id, gpointer data)
+{
+ TimeoutDialog *timeout = data;
+
+ if (response_id == GTK_RESPONSE_DELETE_EVENT) {
+ /* The user closed the dialog or pressed ESC, revert */
+ timeout->response_id = GTK_RESPONSE_CANCEL;
+ } else
+ timeout->response_id = response_id;
+
+ gtk_main_quit ();
+}
+
+static gboolean
+user_says_things_are_ok (MsdXrandrManager *manager, GdkWindow *parent_window)
+{
+ TimeoutDialog timeout;
+ guint timeout_id;
+
+ timeout.manager = manager;
+
+ timeout.dialog = gtk_message_dialog_new (NULL,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Does the display look OK?"));
+
+ timeout.countdown = CONFIRMATION_DIALOG_SECONDS;
+
+ print_countdown_text (&timeout);
+
+ gtk_dialog_add_button (GTK_DIALOG (timeout.dialog), _("_Restore Previous Configuration"), GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (timeout.dialog), _("_Keep This Configuration"), GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_default_response (GTK_DIALOG (timeout.dialog), GTK_RESPONSE_ACCEPT); /* ah, the optimism */
+
+ g_signal_connect (timeout.dialog, "response",
+ G_CALLBACK (timeout_response_cb),
+ &timeout);
+
+ gtk_widget_realize (timeout.dialog);
+
+ if (parent_window)
+ gdk_window_set_transient_for (gtk_widget_get_window (timeout.dialog), parent_window);
+
+ gtk_widget_show_all (timeout.dialog);
+ /* We don't use g_timeout_add_seconds() since we actually care that the user sees "real" second ticks in the dialog */
+ timeout_id = g_timeout_add (1000,
+ timeout_cb,
+ &timeout);
+ gtk_main ();
+
+ gtk_widget_destroy (timeout.dialog);
+ g_source_remove (timeout_id);
+
+ if (timeout.response_id == GTK_RESPONSE_ACCEPT)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+struct confirmation {
+ MsdXrandrManager *manager;
+ GdkWindow *parent_window;
+ guint32 timestamp;
+};
+
+static gboolean
+confirm_with_user_idle_cb (gpointer data)
+{
+ struct confirmation *confirmation = data;
+ char *backup_filename;
+ char *intended_filename;
+
+ backup_filename = mate_rr_config_get_backup_filename ();
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ if (user_says_things_are_ok (confirmation->manager, confirmation->parent_window))
+ unlink (backup_filename);
+ else
+ restore_backup_configuration (confirmation->manager, backup_filename, intended_filename, confirmation->timestamp);
+
+ g_free (confirmation);
+
+ return FALSE;
+}
+
+static void
+queue_confirmation_by_user (MsdXrandrManager *manager, GdkWindow *parent_window, guint32 timestamp)
+{
+ struct confirmation *confirmation;
+
+ confirmation = g_new (struct confirmation, 1);
+ confirmation->manager = manager;
+ confirmation->parent_window = parent_window;
+ confirmation->timestamp = timestamp;
+
+ g_idle_add (confirm_with_user_idle_cb, confirmation);
+}
+
+static gboolean
+try_to_apply_intended_configuration (MsdXrandrManager *manager, GdkWindow *parent_window, guint32 timestamp, GError **error)
+{
+ char *backup_filename;
+ char *intended_filename;
+ gboolean result;
+
+ /* Try to apply the intended configuration */
+
+ backup_filename = mate_rr_config_get_backup_filename ();
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ result = apply_configuration_from_filename (manager, intended_filename, FALSE, timestamp, error);
+ if (!result) {
+ error_message (manager, _("The selected configuration for displays could not be applied"), error ? *error : NULL, NULL);
+ restore_backup_configuration_without_messages (backup_filename, intended_filename);
+ goto out;
+ } else {
+ /* We need to return as quickly as possible, so instead of
+ * confirming with the user right here, we do it in an idle
+ * handler. The caller only expects a status for "could you
+ * change the RANDR configuration?", not "is the user OK with it
+ * as well?".
+ */
+ queue_confirmation_by_user (manager, parent_window, timestamp);
+ }
+
+out:
+ g_free (backup_filename);
+ g_free (intended_filename);
+
+ return result;
+}
+
+/* DBus method for org.mate.SettingsDaemon.XRANDR ApplyConfiguration; see msd-xrandr-manager.xml for the interface definition */
+static gboolean
+msd_xrandr_manager_apply_configuration (MsdXrandrManager *manager,
+ GError **error)
+{
+ return try_to_apply_intended_configuration (manager, NULL, GDK_CURRENT_TIME, error);
+}
+
+/* DBus method for org.mate.SettingsDaemon.XRANDR_2 ApplyConfiguration; see msd-xrandr-manager.xml for the interface definition */
+static gboolean
+msd_xrandr_manager_2_apply_configuration (MsdXrandrManager *manager,
+ gint64 parent_window_id,
+ gint64 timestamp,
+ GError **error)
+{
+ GdkWindow *parent_window;
+ gboolean result;
+
+ if (parent_window_id != 0)
+ parent_window = gdk_window_foreign_new_for_display (gdk_display_get_default (), (GdkNativeWindow) parent_window_id);
+ else
+ parent_window = NULL;
+
+ result = try_to_apply_intended_configuration (manager, parent_window, (guint32) timestamp, error);
+
+ if (parent_window)
+ g_object_unref (parent_window);
+
+ return result;
+}
+
+/* We include this after the definition of msd_xrandr_manager_apply_configuration() so the prototype will already exist */
+#include "msd-xrandr-manager-glue.h"
+
+static gboolean
+is_laptop (MateRRScreen *screen, MateOutputInfo *output)
+{
+ MateRROutput *rr_output;
+
+ rr_output = mate_rr_screen_get_output_by_name (screen, output->name);
+ return mate_rr_output_is_laptop (rr_output);
+}
+
+static gboolean
+get_clone_size (MateRRScreen *screen, int *width, int *height)
+{
+ MateRRMode **modes = mate_rr_screen_list_clone_modes (screen);
+ int best_w, best_h;
+ int i;
+
+ best_w = 0;
+ best_h = 0;
+
+ for (i = 0; modes[i] != NULL; ++i) {
+ MateRRMode *mode = modes[i];
+ int w, h;
+
+ w = mate_rr_mode_get_width (mode);
+ h = mate_rr_mode_get_height (mode);
+
+ if (w * h > best_w * best_h) {
+ best_w = w;
+ best_h = h;
+ }
+ }
+
+ if (best_w > 0 && best_h > 0) {
+ if (width)
+ *width = best_w;
+ if (height)
+ *height = best_h;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+print_output (MateOutputInfo *info)
+{
+ g_print (" Output: %s attached to %s\n", info->display_name, info->name);
+ g_print (" status: %s\n", info->on ? "on" : "off");
+ g_print (" width: %d\n", info->width);
+ g_print (" height: %d\n", info->height);
+ g_print (" rate: %d\n", info->rate);
+ g_print (" position: %d %d\n", info->x, info->y);
+}
+
+static void
+print_configuration (MateRRConfig *config, const char *header)
+{
+ int i;
+
+ g_print ("=== %s Configuration ===\n", header);
+ if (!config) {
+ g_print (" none\n");
+ return;
+ }
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ print_output (config->outputs[i]);
+}
+
+static gboolean
+config_is_all_off (MateRRConfig *config)
+{
+ int j;
+
+ for (j = 0; config->outputs[j] != NULL; ++j) {
+ if (config->outputs[j]->on) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static MateRRConfig *
+make_clone_setup (MateRRScreen *screen)
+{
+ MateRRConfig *result;
+ int width, height;
+ int i;
+
+ if (!get_clone_size (screen, &width, &height))
+ return NULL;
+
+ result = mate_rr_config_new_current (screen);
+
+ for (i = 0; result->outputs[i] != NULL; ++i) {
+ MateOutputInfo *info = result->outputs[i];
+
+ info->on = FALSE;
+ if (info->connected) {
+ MateRROutput *output =
+ mate_rr_screen_get_output_by_name (screen, info->name);
+ MateRRMode **modes = mate_rr_output_list_modes (output);
+ int j;
+ int best_rate = 0;
+
+ for (j = 0; modes[j] != NULL; ++j) {
+ MateRRMode *mode = modes[j];
+ int w, h;
+
+ w = mate_rr_mode_get_width (mode);
+ h = mate_rr_mode_get_height (mode);
+
+ if (w == width && h == height) {
+ int r = mate_rr_mode_get_freq (mode);
+ if (r > best_rate)
+ best_rate = r;
+ }
+ }
+
+ if (best_rate > 0) {
+ info->on = TRUE;
+ info->width = width;
+ info->height = height;
+ info->rate = best_rate;
+ info->rotation = MATE_RR_ROTATION_0;
+ info->x = 0;
+ info->y = 0;
+ }
+ }
+ }
+
+ if (config_is_all_off (result)) {
+ mate_rr_config_free (result);
+ result = NULL;
+ }
+
+ print_configuration (result, "clone setup");
+
+ return result;
+}
+
+static MateRRMode *
+find_best_mode (MateRROutput *output)
+{
+ MateRRMode *preferred;
+ MateRRMode **modes;
+ int best_size;
+ int best_width, best_height, best_rate;
+ int i;
+ MateRRMode *best_mode;
+
+ preferred = mate_rr_output_get_preferred_mode (output);
+ if (preferred)
+ return preferred;
+
+ modes = mate_rr_output_list_modes (output);
+ if (!modes)
+ return NULL;
+
+ best_size = best_width = best_height = best_rate = 0;
+ best_mode = NULL;
+
+ for (i = 0; modes[i] != NULL; i++) {
+ int w, h, r;
+ int size;
+
+ w = mate_rr_mode_get_width (modes[i]);
+ h = mate_rr_mode_get_height (modes[i]);
+ r = mate_rr_mode_get_freq (modes[i]);
+
+ size = w * h;
+
+ if (size > best_size) {
+ best_size = size;
+ best_width = w;
+ best_height = h;
+ best_rate = r;
+ best_mode = modes[i];
+ } else if (size == best_size) {
+ if (r > best_rate) {
+ best_rate = r;
+ best_mode = modes[i];
+ }
+ }
+ }
+
+ return best_mode;
+}
+
+static gboolean
+turn_on (MateRRScreen *screen,
+ MateOutputInfo *info,
+ int x, int y)
+{
+ MateRROutput *output = mate_rr_screen_get_output_by_name (screen, info->name);
+ MateRRMode *mode = find_best_mode (output);
+
+ if (mode) {
+ info->on = TRUE;
+ info->x = x;
+ info->y = y;
+ info->width = mate_rr_mode_get_width (mode);
+ info->height = mate_rr_mode_get_height (mode);
+ info->rotation = MATE_RR_ROTATION_0;
+ info->rate = mate_rr_mode_get_freq (mode);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static MateRRConfig *
+make_laptop_setup (MateRRScreen *screen)
+{
+ /* Turn on the laptop, disable everything else */
+ MateRRConfig *result = mate_rr_config_new_current (screen);
+ int i;
+
+ for (i = 0; result->outputs[i] != NULL; ++i) {
+ MateOutputInfo *info = result->outputs[i];
+
+ if (is_laptop (screen, info)) {
+ if (!turn_on (screen, info, 0, 0)) {
+ mate_rr_config_free (result);
+ result = NULL;
+ break;
+ }
+ }
+ else {
+ info->on = FALSE;
+ }
+ }
+
+ if (config_is_all_off (result)) {
+ mate_rr_config_free (result);
+ result = NULL;
+ }
+
+ print_configuration (result, "Laptop setup");
+
+ /* FIXME - Maybe we should return NULL if there is more than
+ * one connected "laptop" screen?
+ */
+ return result;
+
+}
+
+static int
+turn_on_and_get_rightmost_offset (MateRRScreen *screen, MateOutputInfo *info, int x)
+{
+ if (turn_on (screen, info, x, 0))
+ x += info->width;
+
+ return x;
+}
+
+static MateRRConfig *
+make_xinerama_setup (MateRRScreen *screen)
+{
+ /* Turn on everything that has a preferred mode, and
+ * position it from left to right
+ */
+ MateRRConfig *result = mate_rr_config_new_current (screen);
+ int i;
+ int x;
+
+ x = 0;
+ for (i = 0; result->outputs[i] != NULL; ++i) {
+ MateOutputInfo *info = result->outputs[i];
+
+ if (is_laptop (screen, info))
+ x = turn_on_and_get_rightmost_offset (screen, info, x);
+ }
+
+ for (i = 0; result->outputs[i] != NULL; ++i) {
+ MateOutputInfo *info = result->outputs[i];
+
+ if (info->connected && !is_laptop (screen, info))
+ x = turn_on_and_get_rightmost_offset (screen, info, x);
+ }
+
+ if (config_is_all_off (result)) {
+ mate_rr_config_free (result);
+ result = NULL;
+ }
+
+ print_configuration (result, "xinerama setup");
+
+ return result;
+}
+
+static MateRRConfig *
+make_other_setup (MateRRScreen *screen)
+{
+ /* Turn off all laptops, and make all external monitors clone
+ * from (0, 0)
+ */
+
+ MateRRConfig *result = mate_rr_config_new_current (screen);
+ int i;
+
+ for (i = 0; result->outputs[i] != NULL; ++i) {
+ MateOutputInfo *info = result->outputs[i];
+
+ if (is_laptop (screen, info)) {
+ info->on = FALSE;
+ }
+ else {
+ if (info->connected)
+ turn_on (screen, info, 0, 0);
+ }
+ }
+
+ if (config_is_all_off (result)) {
+ mate_rr_config_free (result);
+ result = NULL;
+ }
+
+ print_configuration (result, "other setup");
+
+ return result;
+}
+
+static GPtrArray *
+sanitize (MsdXrandrManager *manager, GPtrArray *array)
+{
+ int i;
+ GPtrArray *new;
+
+ g_debug ("before sanitizing");
+
+ for (i = 0; i < array->len; ++i) {
+ if (array->pdata[i]) {
+ print_configuration (array->pdata[i], "before");
+ }
+ }
+
+
+ /* Remove configurations that are duplicates of
+ * configurations earlier in the cycle
+ */
+ for (i = 0; i < array->len; i++) {
+ int j;
+
+ for (j = i + 1; j < array->len; j++) {
+ MateRRConfig *this = array->pdata[j];
+ MateRRConfig *other = array->pdata[i];
+
+ if (this && other && mate_rr_config_equal (this, other)) {
+ g_debug ("removing duplicate configuration");
+ mate_rr_config_free (this);
+ array->pdata[j] = NULL;
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < array->len; ++i) {
+ MateRRConfig *config = array->pdata[i];
+
+ if (config && config_is_all_off (config)) {
+ g_debug ("removing configuration as all outputs are off");
+ mate_rr_config_free (array->pdata[i]);
+ array->pdata[i] = NULL;
+ }
+ }
+
+ /* Do a final sanitization pass. This will remove configurations that
+ * don't fit in the framebuffer's Virtual size.
+ */
+
+ for (i = 0; i < array->len; i++) {
+ MateRRConfig *config = array->pdata[i];
+
+ if (config) {
+ GError *error;
+
+ error = NULL;
+ if (!mate_rr_config_applicable (config, manager->priv->rw_screen, &error)) { /* NULL-GError */
+ g_debug ("removing configuration which is not applicable because %s", error->message);
+ g_error_free (error);
+
+ mate_rr_config_free (config);
+ array->pdata[i] = NULL;
+ }
+ }
+ }
+
+ /* Remove NULL configurations */
+ new = g_ptr_array_new ();
+
+ for (i = 0; i < array->len; ++i) {
+ if (array->pdata[i]) {
+ g_ptr_array_add (new, array->pdata[i]);
+ print_configuration (array->pdata[i], "Final");
+ }
+ }
+
+ if (new->len > 0) {
+ g_ptr_array_add (new, NULL);
+ } else {
+ g_ptr_array_free (new, TRUE);
+ new = NULL;
+ }
+
+ g_ptr_array_free (array, TRUE);
+
+ return new;
+}
+
+static void
+generate_fn_f7_configs (MsdXrandrManager *mgr)
+{
+ GPtrArray *array = g_ptr_array_new ();
+ MateRRScreen *screen = mgr->priv->rw_screen;
+
+ g_debug ("Generating configurations");
+
+ /* Free any existing list of configurations */
+ if (mgr->priv->fn_f7_configs) {
+ int i;
+
+ for (i = 0; mgr->priv->fn_f7_configs[i] != NULL; ++i)
+ mate_rr_config_free (mgr->priv->fn_f7_configs[i]);
+ g_free (mgr->priv->fn_f7_configs);
+
+ mgr->priv->fn_f7_configs = NULL;
+ mgr->priv->current_fn_f7_config = -1;
+ }
+
+ g_ptr_array_add (array, mate_rr_config_new_current (screen));
+ g_ptr_array_add (array, make_clone_setup (screen));
+ g_ptr_array_add (array, make_xinerama_setup (screen));
+ g_ptr_array_add (array, make_laptop_setup (screen));
+ g_ptr_array_add (array, make_other_setup (screen));
+
+ array = sanitize (mgr, array);
+
+ if (array) {
+ mgr->priv->fn_f7_configs = (MateRRConfig **)g_ptr_array_free (array, FALSE);
+ mgr->priv->current_fn_f7_config = 0;
+ }
+}
+
+static void
+error_message (MsdXrandrManager *mgr, const char *primary_text, GError *error_to_display, const char *secondary_text)
+{
+#ifdef HAVE_LIBMATENOTIFY
+ MsdXrandrManagerPrivate *priv = mgr->priv;
+ NotifyNotification *notification;
+
+ g_assert (error_to_display == NULL || secondary_text == NULL);
+
+ if (priv->status_icon)
+ notification = notify_notification_new_with_status_icon (primary_text,
+ error_to_display ? error_to_display->message : secondary_text,
+ MSD_XRANDR_ICON_NAME,
+ priv->status_icon);
+ else
+ notification = notify_notification_new (primary_text,
+ error_to_display ? error_to_display->message : secondary_text,
+ MSD_XRANDR_ICON_NAME,
+ NULL);
+
+ notify_notification_show (notification, NULL); /* NULL-GError */
+#else
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s", primary_text);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s",
+ error_to_display ? error_to_display->message : secondary_text);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+#endif /* HAVE_LIBMATENOTIFY */
+}
+
+static void
+handle_fn_f7 (MsdXrandrManager *mgr, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = mgr->priv;
+ MateRRScreen *screen = priv->rw_screen;
+ MateRRConfig *current;
+ GError *error;
+
+ /* Theory of fn-F7 operation
+ *
+ * We maintain a datastructure "fn_f7_status", that contains
+ * a list of MateRRConfig's. Each of the MateRRConfigs has a
+ * mode (or "off") for each connected output.
+ *
+ * When the user hits fn-F7, we cycle to the next MateRRConfig
+ * in the data structure. If the data structure does not exist, it
+ * is generated. If the configs in the data structure do not match
+ * the current hardware reality, it is regenerated.
+ *
+ */
+ g_debug ("Handling fn-f7");
+
+ log_open ();
+ log_msg ("Handling XF86Display hotkey - timestamp %u\n", timestamp);
+
+ error = NULL;
+ if (!mate_rr_screen_refresh (screen, &error) && error) {
+ char *str;
+
+ str = g_strdup_printf (_("Could not refresh the screen information: %s"), error->message);
+ g_error_free (error);
+
+ log_msg ("%s\n", str);
+ error_message (mgr, str, NULL, _("Trying to switch the monitor configuration anyway."));
+ g_free (str);
+ }
+
+ if (!priv->fn_f7_configs) {
+ log_msg ("Generating stock configurations:\n");
+ generate_fn_f7_configs (mgr);
+ log_configurations (priv->fn_f7_configs);
+ }
+
+ current = mate_rr_config_new_current (screen);
+
+ if (priv->fn_f7_configs &&
+ (!mate_rr_config_match (current, priv->fn_f7_configs[0]) ||
+ !mate_rr_config_equal (current, priv->fn_f7_configs[mgr->priv->current_fn_f7_config]))) {
+ /* Our view of the world is incorrect, so regenerate the
+ * configurations
+ */
+ generate_fn_f7_configs (mgr);
+ log_msg ("Regenerated stock configurations:\n");
+ log_configurations (priv->fn_f7_configs);
+ }
+
+ mate_rr_config_free (current);
+
+ if (priv->fn_f7_configs) {
+ guint32 server_timestamp;
+ gboolean success;
+
+ mgr->priv->current_fn_f7_config++;
+
+ if (priv->fn_f7_configs[mgr->priv->current_fn_f7_config] == NULL)
+ mgr->priv->current_fn_f7_config = 0;
+
+ g_debug ("cycling to next configuration (%d)", mgr->priv->current_fn_f7_config);
+
+ print_configuration (priv->fn_f7_configs[mgr->priv->current_fn_f7_config], "new config");
+
+ g_debug ("applying");
+
+ /* See https://bugzilla.gnome.org/show_bug.cgi?id=610482
+ *
+ * Sometimes we'll get two rapid XF86Display keypress events,
+ * but their timestamps will be out of order with respect to the
+ * RANDR timestamps. This *may* be due to stupid BIOSes sending
+ * out display-switch keystrokes "to make Windows work".
+ *
+ * The X server will error out if the timestamp provided is
+ * older than a previous change configuration timestamp. We
+ * assume here that we do want this event to go through still,
+ * since kernel timestamps may be skewed wrt the X server.
+ */
+ mate_rr_screen_get_timestamps (screen, NULL, &server_timestamp);
+ if (timestamp < server_timestamp)
+ timestamp = server_timestamp;
+
+ success = apply_configuration_and_display_error (mgr, priv->fn_f7_configs[mgr->priv->current_fn_f7_config], timestamp);
+
+ if (success) {
+ log_msg ("Successfully switched to configuration (timestamp %u):\n", timestamp);
+ log_configuration (priv->fn_f7_configs[mgr->priv->current_fn_f7_config]);
+ }
+ }
+ else {
+ g_debug ("no configurations generated");
+ }
+
+ log_close ();
+
+ g_debug ("done handling fn-f7");
+}
+
+static MateOutputInfo *
+get_laptop_output_info (MateRRScreen *screen, MateRRConfig *config)
+{
+ int i;
+
+ for (i = 0; config->outputs[i] != NULL; i++) {
+ MateOutputInfo *info;
+
+ info = config->outputs[i];
+ if (is_laptop (screen, info))
+ return info;
+ }
+
+ return NULL;
+
+}
+
+static MateRRRotation
+get_next_rotation (MateRRRotation allowed_rotations, MateRRRotation current_rotation)
+{
+ int i;
+ int current_index;
+
+ /* First, find the index of the current rotation */
+
+ current_index = -1;
+
+ for (i = 0; i < G_N_ELEMENTS (possible_rotations); i++) {
+ MateRRRotation r;
+
+ r = possible_rotations[i];
+ if (r == current_rotation) {
+ current_index = i;
+ break;
+ }
+ }
+
+ if (current_index == -1) {
+ /* Huh, the current_rotation was not one of the supported rotations. Bail out. */
+ return current_rotation;
+ }
+
+ /* Then, find the next rotation that is allowed */
+
+ i = (current_index + 1) % G_N_ELEMENTS (possible_rotations);
+
+ while (1) {
+ MateRRRotation r;
+
+ r = possible_rotations[i];
+ if (r == current_rotation) {
+ /* We wrapped around and no other rotation is suported. Bummer. */
+ return current_rotation;
+ } else if (r & allowed_rotations)
+ return r;
+
+ i = (i + 1) % G_N_ELEMENTS (possible_rotations);
+ }
+}
+
+/* We use this when the XF86RotateWindows key is pressed. That key is present
+ * on some tablet PCs; they use it so that the user can rotate the tablet
+ * easily.
+ */
+static void
+handle_rotate_windows (MsdXrandrManager *mgr, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = mgr->priv;
+ MateRRScreen *screen = priv->rw_screen;
+ MateRRConfig *current;
+ MateOutputInfo *rotatable_output_info;
+ int num_allowed_rotations;
+ MateRRRotation allowed_rotations;
+ MateRRRotation next_rotation;
+
+ g_debug ("Handling XF86RotateWindows");
+
+ /* Which output? */
+
+ current = mate_rr_config_new_current (screen);
+
+ rotatable_output_info = get_laptop_output_info (screen, current);
+ if (rotatable_output_info == NULL) {
+ g_debug ("No laptop outputs found to rotate; XF86RotateWindows key will do nothing");
+ goto out;
+ }
+
+ /* Which rotation? */
+
+ get_allowed_rotations_for_output (current, priv->rw_screen, rotatable_output_info, &num_allowed_rotations, &allowed_rotations);
+ next_rotation = get_next_rotation (allowed_rotations, rotatable_output_info->rotation);
+
+ if (next_rotation == rotatable_output_info->rotation) {
+ g_debug ("No rotations are supported other than the current one; XF86RotateWindows key will do nothing");
+ goto out;
+ }
+
+ /* Rotate */
+
+ rotatable_output_info->rotation = next_rotation;
+
+ apply_configuration_and_display_error (mgr, current, timestamp);
+
+out:
+ mate_rr_config_free (current);
+}
+
+static GdkFilterReturn
+event_filter (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ MsdXrandrManager *manager = data;
+ XEvent *xev = (XEvent *) xevent;
+
+ if (!manager->priv->running)
+ return GDK_FILTER_CONTINUE;
+
+ /* verify we have a key event */
+ if (xev->xany.type != KeyPress && xev->xany.type != KeyRelease)
+ return GDK_FILTER_CONTINUE;
+
+ if (xev->xany.type == KeyPress) {
+ if (xev->xkey.keycode == manager->priv->switch_video_mode_keycode)
+ handle_fn_f7 (manager, xev->xkey.time);
+ else if (xev->xkey.keycode == manager->priv->rotate_windows_keycode)
+ handle_rotate_windows (manager, xev->xkey.time);
+
+ return GDK_FILTER_CONTINUE;
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void
+refresh_tray_icon_menu_if_active (MsdXrandrManager *manager, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = manager->priv;
+
+ if (priv->popup_menu) {
+ gtk_menu_shell_cancel (GTK_MENU_SHELL (priv->popup_menu)); /* status_icon_popup_menu_selection_done_cb() will free everything */
+ status_icon_popup_menu (manager, 0, timestamp);
+ }
+}
+
+static void
+auto_configure_outputs (MsdXrandrManager *manager, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = manager->priv;
+ MateRRConfig *config;
+ int i;
+ GList *just_turned_on;
+ GList *l;
+ int x;
+ GError *error;
+ gboolean applicable;
+
+ config = mate_rr_config_new_current (priv->rw_screen);
+
+ /* For outputs that are connected and on (i.e. they have a CRTC assigned
+ * to them, so they are getting a signal), we leave them as they are
+ * with their current modes.
+ *
+ * For other outputs, we will turn on connected-but-off outputs and turn
+ * off disconnected-but-on outputs.
+ *
+ * FIXME: If an output remained connected+on, it would be nice to ensure
+ * that the output's CRTCs still has a reasonable mode (think of
+ * changing one monitor for another with different capabilities).
+ */
+
+ just_turned_on = NULL;
+
+ for (i = 0; config->outputs[i] != NULL; i++) {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->connected && !output->on) {
+ output->on = TRUE;
+ output->rotation = MATE_RR_ROTATION_0;
+ just_turned_on = g_list_prepend (just_turned_on, GINT_TO_POINTER (i));
+ } else if (!output->connected && output->on)
+ output->on = FALSE;
+ }
+
+ /* Now, lay out the outputs from left to right. Put first the outputs
+ * which remained on; put last the outputs that were newly turned on.
+ */
+
+ x = 0;
+
+ /* First, outputs that remained on */
+
+ for (i = 0; config->outputs[i] != NULL; i++) {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (g_list_find (just_turned_on, GINT_TO_POINTER (i)))
+ continue;
+
+ if (output->on) {
+ g_assert (output->connected);
+
+ output->x = x;
+ output->y = 0;
+
+ x += output->width;
+ }
+ }
+
+ /* Second, outputs that were newly-turned on */
+
+ for (l = just_turned_on; l; l = l->next) {
+ MateOutputInfo *output;
+
+ i = GPOINTER_TO_INT (l->data);
+ output = config->outputs[i];
+
+ g_assert (output->on && output->connected);
+
+ output->x = x;
+ output->y = 0;
+
+ /* since the output was off, use its preferred width/height (it doesn't have a real width/height yet) */
+ output->width = output->pref_width;
+ output->height = output->pref_height;
+
+ x += output->width;
+ }
+
+ /* Check if we have a large enough framebuffer size. If not, turn off
+ * outputs from right to left until we reach a usable size.
+ */
+
+ just_turned_on = g_list_reverse (just_turned_on); /* now the outputs here are from right to left */
+
+ l = just_turned_on;
+ while (1) {
+ MateOutputInfo *output;
+ gboolean is_bounds_error;
+
+ error = NULL;
+ applicable = mate_rr_config_applicable (config, priv->rw_screen, &error);
+
+ if (applicable)
+ break;
+
+ is_bounds_error = g_error_matches (error, MATE_RR_ERROR, MATE_RR_ERROR_BOUNDS_ERROR);
+ g_error_free (error);
+
+ if (!is_bounds_error)
+ break;
+
+ if (l) {
+ i = GPOINTER_TO_INT (l->data);
+ l = l->next;
+
+ output = config->outputs[i];
+ output->on = FALSE;
+ } else
+ break;
+ }
+
+ /* Apply the configuration! */
+
+ if (applicable)
+ apply_configuration_and_display_error (manager, config, timestamp);
+
+ g_list_free (just_turned_on);
+ mate_rr_config_free (config);
+
+ /* Finally, even though we did a best-effort job in sanitizing the
+ * outputs, we don't know the physical layout of the monitors. We'll
+ * start the display capplet so that the user can tweak things to his
+ * liking.
+ */
+
+#if 0
+ /* FIXME: This is disabled for now. The capplet is not a single-instance application.
+ * If you do this:
+ *
+ * 1. Start the display capplet
+ *
+ * 2. Plug an extra monitor
+ *
+ * 3. Hit the "Detect displays" button
+ *
+ * Then we will get a RANDR event because X re-probes the outputs. We don't want to
+ * start up a second display capplet right there!
+ */
+
+ run_display_capplet (NULL);
+#endif
+}
+
+static void
+apply_color_profiles (void)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ /* run the mate-color-manager apply program */
+ ret = g_spawn_command_line_async (BINDIR "/gcm-apply", &error);
+ if (!ret) {
+ /* only print the warning if the binary is installed */
+ if (error->code != G_SPAWN_ERROR_NOENT) {
+ g_warning ("failed to apply color profiles: %s", error->message);
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+on_randr_event (MateRRScreen *screen, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+ MsdXrandrManagerPrivate *priv = manager->priv;
+ guint32 change_timestamp, config_timestamp;
+
+ if (!priv->running)
+ return;
+
+ mate_rr_screen_get_timestamps (screen, &change_timestamp, &config_timestamp);
+
+ log_open ();
+ log_msg ("Got RANDR event with timestamps change=%u %c config=%u\n",
+ change_timestamp,
+ timestamp_relationship (change_timestamp, config_timestamp),
+ config_timestamp);
+
+ if (change_timestamp >= config_timestamp) {
+ /* The event is due to an explicit configuration change.
+ *
+ * If the change was performed by us, then we need to do nothing.
+ *
+ * If the change was done by some other X client, we don't need
+ * to do anything, either; the screen is already configured.
+ */
+ show_timestamps_dialog (manager, "ignoring since change > config");
+ log_msg (" Ignoring event since change >= config\n");
+ } else {
+ /* Here, config_timestamp > change_timestamp. This means that
+ * the screen got reconfigured because of hotplug/unplug; the X
+ * server is just notifying us, and we need to configure the
+ * outputs in a sane way.
+ */
+
+ char *intended_filename;
+ GError *error;
+ gboolean success;
+
+ show_timestamps_dialog (manager, "need to deal with reconfiguration, as config > change");
+
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ error = NULL;
+ success = apply_configuration_from_filename (manager, intended_filename, TRUE, config_timestamp, &error);
+ g_free (intended_filename);
+
+ if (!success) {
+ /* We don't bother checking the error type.
+ *
+ * Both G_FILE_ERROR_NOENT and
+ * MATE_RR_ERROR_NO_MATCHING_CONFIG would mean, "there
+ * was no configuration to apply, or none that matched
+ * the current outputs", and in that case we need to run
+ * our fallback.
+ *
+ * Any other error means "we couldn't do the smart thing
+ * of using a previously- saved configuration, anyway,
+ * for some other reason. In that case, we also need to
+ * run our fallback to avoid leaving the user with a
+ * bogus configuration.
+ */
+
+ if (error)
+ g_error_free (error);
+
+ if (config_timestamp != priv->last_config_timestamp) {
+ priv->last_config_timestamp = config_timestamp;
+ auto_configure_outputs (manager, config_timestamp);
+ log_msg (" Automatically configured outputs to deal with event\n");
+ } else
+ log_msg (" Ignored event as old and new config timestamps are the same\n");
+ } else
+ log_msg ("Applied stored configuration to deal with event\n");
+ }
+
+ /* poke mate-color-manager */
+ apply_color_profiles ();
+
+ refresh_tray_icon_menu_if_active (manager, MAX (change_timestamp, config_timestamp));
+
+ log_close ();
+}
+
+static void
+run_display_capplet (GtkWidget *widget)
+{
+ GdkScreen *screen;
+ GError *error;
+
+ if (widget)
+ screen = gtk_widget_get_screen (widget);
+ else
+ screen = gdk_screen_get_default ();
+
+ error = NULL;
+ if (!gdk_spawn_command_line_on_screen (screen, MSD_XRANDR_DISPLAY_CAPPLET, &error)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new_with_markup (NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "<span weight=\"bold\" size=\"larger\">"
+ "Display configuration could not be run"
+ "</span>\n\n"
+ "%s", error->message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_error_free (error);
+ }
+}
+
+static void
+popup_menu_configure_display_cb (GtkMenuItem *item, gpointer data)
+{
+ run_display_capplet (GTK_WIDGET (item));
+}
+
+static void
+status_icon_popup_menu_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+
+ gtk_widget_destroy (priv->popup_menu);
+ priv->popup_menu = NULL;
+
+ mate_rr_labeler_hide (priv->labeler);
+ g_object_unref (priv->labeler);
+ priv->labeler = NULL;
+
+ mate_rr_config_free (priv->configuration);
+ priv->configuration = NULL;
+}
+
+#define OUTPUT_TITLE_ITEM_BORDER 2
+#define OUTPUT_TITLE_ITEM_PADDING 4
+
+/* This is an expose-event hander for the title label for each MateRROutput.
+ * We want each title to have a colored background, so we paint that background, then
+ * return FALSE to let GtkLabel expose itself (i.e. paint the label's text), and then
+ * we have a signal_connect_after handler as well. See the comments below
+ * to see why that "after" handler is needed.
+ */
+static gboolean
+output_title_label_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ MateOutputInfo *output;
+ GdkColor color;
+ cairo_t *cr;
+ GtkAllocation allocation;
+
+ g_assert (GTK_IS_LABEL (widget));
+
+ output = g_object_get_data (G_OBJECT (widget), "output");
+ g_assert (output != NULL);
+
+ g_assert (priv->labeler != NULL);
+
+ /* Draw a black rectangular border, filled with the color that corresponds to this output */
+
+ mate_rr_labeler_get_color_for_output (priv->labeler, output, &color);
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, OUTPUT_TITLE_ITEM_BORDER);
+ gtk_widget_get_allocation (widget, &allocation);
+ cairo_rectangle (cr,
+ allocation.x + OUTPUT_TITLE_ITEM_BORDER / 2.0,
+ allocation.y + OUTPUT_TITLE_ITEM_BORDER / 2.0,
+ allocation.width - OUTPUT_TITLE_ITEM_BORDER,
+ allocation.height - OUTPUT_TITLE_ITEM_BORDER);
+ cairo_stroke (cr);
+
+ gdk_cairo_set_source_color (cr, &color);
+ cairo_rectangle (cr,
+ allocation.x + OUTPUT_TITLE_ITEM_BORDER,
+ allocation.y + OUTPUT_TITLE_ITEM_BORDER,
+ allocation.width - 2 * OUTPUT_TITLE_ITEM_BORDER,
+ allocation.height - 2 * OUTPUT_TITLE_ITEM_BORDER);
+
+ cairo_fill (cr);
+
+ /* We want the label to always show up as if it were sensitive
+ * ("style->fg[GTK_STATE_NORMAL]"), even though the label is insensitive
+ * due to being inside an insensitive menu item. So, here we have a
+ * HACK in which we frob the label's state directly. GtkLabel's expose
+ * handler will be run after this function, so it will think that the
+ * label is in GTK_STATE_NORMAL. We reset the label's state back to
+ * insensitive in output_title_label_after_expose_event_cb().
+ *
+ * Yay for fucking with GTK+'s internals.
+ */
+
+ gtk_widget_set_state (widget, GTK_STATE_NORMAL);
+
+ return FALSE;
+}
+
+/* See the comment in output_title_event_box_expose_event_cb() about this funny label widget */
+static gboolean
+output_title_label_after_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ g_assert (GTK_IS_LABEL (widget));
+ gtk_widget_set_state (widget, GTK_STATE_INSENSITIVE);
+
+ return FALSE;
+}
+
+static void
+title_item_size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer data)
+{
+ /* When GtkMenu does size_request on its items, it asks them for their "toggle size",
+ * which will be non-zero when there are check/radio items. GtkMenu remembers
+ * the largest of those sizes. During the size_allocate pass, GtkMenu calls
+ * gtk_menu_item_toggle_size_allocate() with that value, to tell the menu item
+ * that it should later paint its child a bit to the right of its edge.
+ *
+ * However, we want the "title" menu items for each RANDR output to span the *whole*
+ * allocation of the menu item, not just the "allocation minus toggle" area.
+ *
+ * So, we let the menu item size_allocate itself as usual, but this
+ * callback gets run afterward. Here we hack a toggle size of 0 into
+ * the menu item, and size_allocate it by hand *again*. We also need to
+ * avoid recursing into this function.
+ */
+
+ g_assert (GTK_IS_MENU_ITEM (widget));
+
+ gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (widget), 0);
+
+ g_signal_handlers_block_by_func (widget, title_item_size_allocate_cb, NULL);
+
+ /* Sigh. There is no way to turn on GTK_ALLOC_NEEDED outside of GTK+
+ * itself; also, since calling size_allocate on a widget with the same
+ * allcation is a no-op, we need to allocate with a "different" size
+ * first.
+ */
+
+ allocation->width++;
+ gtk_widget_size_allocate (widget, allocation);
+
+ allocation->width--;
+ gtk_widget_size_allocate (widget, allocation);
+
+ g_signal_handlers_unblock_by_func (widget, title_item_size_allocate_cb, NULL);
+}
+
+static GtkWidget *
+make_menu_item_for_output_title (MsdXrandrManager *manager, MateOutputInfo *output)
+{
+ GtkWidget *item;
+ GtkWidget *label;
+ char *str;
+ GdkColor black = { 0, 0, 0, 0 };
+
+ item = gtk_menu_item_new ();
+
+ g_signal_connect (item, "size-allocate",
+ G_CALLBACK (title_item_size_allocate_cb), NULL);
+
+ str = g_markup_printf_escaped ("<b>%s</b>", output->display_name);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ g_free (str);
+
+ /* Make the label explicitly black. We don't want it to follow the
+ * theme's colors, since the label is always shown against a light
+ * pastel background. See bgo#556050
+ */
+ gtk_widget_modify_fg (label, gtk_widget_get_state (label), &black);
+
+ /* Add padding around the label to fit the box that we'll draw for color-coding */
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label),
+ OUTPUT_TITLE_ITEM_BORDER + OUTPUT_TITLE_ITEM_PADDING,
+ OUTPUT_TITLE_ITEM_BORDER + OUTPUT_TITLE_ITEM_PADDING);
+
+ gtk_container_add (GTK_CONTAINER (item), label);
+
+ /* We want to paint a colored box as the background of the label, so we connect
+ * to its expose-event signal. See the comment in *** to see why need to connect
+ * to the label both 'before' and 'after'.
+ */
+ g_signal_connect (label, "expose-event",
+ G_CALLBACK (output_title_label_expose_event_cb), manager);
+ g_signal_connect_after (label, "expose-event",
+ G_CALLBACK (output_title_label_after_expose_event_cb), manager);
+
+ g_object_set_data (G_OBJECT (label), "output", output);
+
+ gtk_widget_set_sensitive (item, FALSE); /* the title is not selectable */
+ gtk_widget_show_all (item);
+
+ return item;
+}
+
+static void
+get_allowed_rotations_for_output (MateRRConfig *config,
+ MateRRScreen *rr_screen,
+ MateOutputInfo *output,
+ int *out_num_rotations,
+ MateRRRotation *out_rotations)
+{
+ MateRRRotation current_rotation;
+ int i;
+
+ *out_num_rotations = 0;
+ *out_rotations = 0;
+
+ current_rotation = output->rotation;
+
+ /* Yay for brute force */
+
+ for (i = 0; i < G_N_ELEMENTS (possible_rotations); i++) {
+ MateRRRotation rotation_to_test;
+
+ rotation_to_test = possible_rotations[i];
+
+ output->rotation = rotation_to_test;
+
+ if (mate_rr_config_applicable (config, rr_screen, NULL)) { /* NULL-GError */
+ (*out_num_rotations)++;
+ (*out_rotations) |= rotation_to_test;
+ }
+ }
+
+ output->rotation = current_rotation;
+
+ if (*out_num_rotations == 0 || *out_rotations == 0) {
+ g_warning ("Huh, output %p says it doesn't support any rotations, and yet it has a current rotation?", output);
+ *out_num_rotations = 1;
+ *out_rotations = output->rotation;
+ }
+}
+
+static void
+add_unsupported_rotation_item (MsdXrandrManager *manager)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ GtkWidget *item;
+ GtkWidget *label;
+ gchar *markup;
+
+ item = gtk_menu_item_new ();
+
+ label = gtk_label_new (NULL);
+ markup = g_strdup_printf ("<i>%s</i>", _("Rotation not supported"));
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+ gtk_container_add (GTK_CONTAINER (item), label);
+
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+}
+
+static void
+ensure_current_configuration_is_saved (void)
+{
+ MateRRScreen *rr_screen;
+ MateRRConfig *rr_config;
+
+ /* Normally, mate_rr_config_save() creates a backup file based on the
+ * old monitors.xml. However, if *that* file didn't exist, there is
+ * nothing from which to create a backup. So, here we'll save the
+ * current/unchanged configuration and then let our caller call
+ * mate_rr_config_save() again with the new/changed configuration, so
+ * that there *will* be a backup file in the end.
+ */
+
+ rr_screen = mate_rr_screen_new (gdk_screen_get_default (), NULL, NULL, NULL); /* NULL-GError */
+ if (!rr_screen)
+ return;
+
+ rr_config = mate_rr_config_new_current (rr_screen);
+ mate_rr_config_save (rr_config, NULL); /* NULL-GError */
+
+ mate_rr_config_free (rr_config);
+ mate_rr_screen_destroy (rr_screen);
+}
+
+static void
+output_rotation_item_activate_cb (GtkCheckMenuItem *item, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ MateOutputInfo *output;
+ MateRRRotation rotation;
+ GError *error;
+
+ /* Not interested in deselected items */
+ if (!gtk_check_menu_item_get_active (item))
+ return;
+
+ ensure_current_configuration_is_saved ();
+
+ output = g_object_get_data (G_OBJECT (item), "output");
+ rotation = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "rotation"));
+
+ output->rotation = rotation;
+
+ error = NULL;
+ if (!mate_rr_config_save (priv->configuration, &error)) {
+ error_message (manager, _("Could not save monitor configuration"), error, NULL);
+ if (error)
+ g_error_free (error);
+
+ return;
+ }
+
+ try_to_apply_intended_configuration (manager, NULL, gtk_get_current_event_time (), NULL); /* NULL-GError */
+}
+
+static void
+add_items_for_rotations (MsdXrandrManager *manager, MateOutputInfo *output, MateRRRotation allowed_rotations)
+{
+ typedef struct {
+ MateRRRotation rotation;
+ const char * name;
+ } RotationInfo;
+ static const RotationInfo rotations[] = {
+ { MATE_RR_ROTATION_0, N_("Normal") },
+ { MATE_RR_ROTATION_90, N_("Left") },
+ { MATE_RR_ROTATION_270, N_("Right") },
+ { MATE_RR_ROTATION_180, N_("Upside Down") },
+ /* We don't allow REFLECT_X or REFLECT_Y for now, as mate-display-properties doesn't allow them, either */
+ };
+
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ int i;
+ GSList *group;
+ GtkWidget *active_item;
+ gulong active_item_activate_id;
+
+ group = NULL;
+ active_item = NULL;
+ active_item_activate_id = 0;
+
+ for (i = 0; i < G_N_ELEMENTS (rotations); i++) {
+ MateRRRotation rot;
+ GtkWidget *item;
+ gulong activate_id;
+
+ rot = rotations[i].rotation;
+
+ if ((allowed_rotations & rot) == 0) {
+ /* don't display items for rotations which are
+ * unavailable. Their availability is not under the
+ * user's control, anyway.
+ */
+ continue;
+ }
+
+ item = gtk_radio_menu_item_new_with_label (group, _(rotations[i].name));
+ gtk_widget_show_all (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ g_object_set_data (G_OBJECT (item), "output", output);
+ g_object_set_data (G_OBJECT (item), "rotation", GINT_TO_POINTER (rot));
+
+ activate_id = g_signal_connect (item, "activate",
+ G_CALLBACK (output_rotation_item_activate_cb), manager);
+
+ group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+
+ if (rot == output->rotation) {
+ active_item = item;
+ active_item_activate_id = activate_id;
+ }
+ }
+
+ if (active_item) {
+ /* Block the signal temporarily so our callback won't be called;
+ * we are just setting up the UI.
+ */
+ g_signal_handler_block (active_item, active_item_activate_id);
+
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (active_item), TRUE);
+
+ g_signal_handler_unblock (active_item, active_item_activate_id);
+ }
+
+}
+
+static void
+add_rotation_items_for_output (MsdXrandrManager *manager, MateOutputInfo *output)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ int num_rotations;
+ MateRRRotation rotations;
+
+ get_allowed_rotations_for_output (priv->configuration, priv->rw_screen, output, &num_rotations, &rotations);
+
+ if (num_rotations == 1)
+ add_unsupported_rotation_item (manager);
+ else
+ add_items_for_rotations (manager, output, rotations);
+}
+
+static void
+add_menu_items_for_output (MsdXrandrManager *manager, MateOutputInfo *output)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ GtkWidget *item;
+
+ item = make_menu_item_for_output_title (manager, output);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ add_rotation_items_for_output (manager, output);
+}
+
+static void
+add_menu_items_for_outputs (MsdXrandrManager *manager)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ int i;
+
+ for (i = 0; priv->configuration->outputs[i] != NULL; i++) {
+ if (priv->configuration->outputs[i]->connected)
+ add_menu_items_for_output (manager, priv->configuration->outputs[i]);
+ }
+}
+
+static void
+status_icon_popup_menu (MsdXrandrManager *manager, guint button, guint32 timestamp)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+ GtkWidget *item;
+
+ g_assert (priv->configuration == NULL);
+ priv->configuration = mate_rr_config_new_current (priv->rw_screen);
+
+ g_assert (priv->labeler == NULL);
+ priv->labeler = mate_rr_labeler_new (priv->configuration);
+
+ g_assert (priv->popup_menu == NULL);
+ priv->popup_menu = gtk_menu_new ();
+
+ add_menu_items_for_outputs (manager);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Configure Display Settingsā€¦"));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (popup_menu_configure_display_cb), manager);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+ g_signal_connect (priv->popup_menu, "selection-done",
+ G_CALLBACK (status_icon_popup_menu_selection_done_cb), manager);
+
+ gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
+ gtk_status_icon_position_menu,
+ priv->status_icon, button, timestamp);
+}
+
+static void
+status_icon_activate_cb (GtkStatusIcon *status_icon, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+
+ /* Suck; we don't get a proper button/timestamp */
+ status_icon_popup_menu (manager, 0, gtk_get_current_event_time ());
+}
+
+static void
+status_icon_popup_menu_cb (GtkStatusIcon *status_icon, guint button, guint32 timestamp, gpointer data)
+{
+ MsdXrandrManager *manager = MSD_XRANDR_MANAGER (data);
+
+ status_icon_popup_menu (manager, button, timestamp);
+}
+
+static void
+status_icon_start (MsdXrandrManager *manager)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+
+ /* Ideally, we should detect if we are on a tablet and only display
+ * the icon in that case.
+ */
+ if (!priv->status_icon) {
+ priv->status_icon = gtk_status_icon_new_from_icon_name (MSD_XRANDR_ICON_NAME);
+ gtk_status_icon_set_tooltip_text (priv->status_icon, _("Configure display settings"));
+
+ g_signal_connect (priv->status_icon, "activate",
+ G_CALLBACK (status_icon_activate_cb), manager);
+ g_signal_connect (priv->status_icon, "popup-menu",
+ G_CALLBACK (status_icon_popup_menu_cb), manager);
+ }
+}
+
+static void
+status_icon_stop (MsdXrandrManager *manager)
+{
+ struct MsdXrandrManagerPrivate *priv = manager->priv;
+
+ if (priv->status_icon) {
+ g_signal_handlers_disconnect_by_func (
+ priv->status_icon, G_CALLBACK (status_icon_activate_cb), manager);
+ g_signal_handlers_disconnect_by_func (
+ priv->status_icon, G_CALLBACK (status_icon_popup_menu_cb), manager);
+
+ /* hide the icon before unreffing it; otherwise we will leak
+ whitespace in the notification area due to a bug in there */
+ gtk_status_icon_set_visible (priv->status_icon, FALSE);
+ g_object_unref (priv->status_icon);
+ priv->status_icon = NULL;
+ }
+}
+
+static void
+start_or_stop_icon (MsdXrandrManager *manager)
+{
+ if (mateconf_client_get_bool (manager->priv->client, CONF_KEY_SHOW_NOTIFICATION_ICON, NULL)) {
+ status_icon_start (manager);
+ }
+ else {
+ status_icon_stop (manager);
+ }
+}
+
+static void
+on_config_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ MsdXrandrManager *manager)
+{
+ if (strcmp (entry->key, CONF_KEY_SHOW_NOTIFICATION_ICON) == 0)
+ start_or_stop_icon (manager);
+}
+
+static gboolean
+apply_intended_configuration (MsdXrandrManager *manager, const char *intended_filename, guint32 timestamp)
+{
+ GError *my_error;
+ gboolean result;
+
+ my_error = NULL;
+ result = apply_configuration_from_filename (manager, intended_filename, FALSE, timestamp, &my_error);
+ if (!result) {
+ if (my_error) {
+ if (!g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ error_message (manager, _("Could not apply the stored configuration for monitors"), my_error, NULL);
+
+ g_error_free (my_error);
+ }
+ }
+
+ return result;
+}
+
+static void
+apply_default_boot_configuration (MsdXrandrManager *mgr, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = mgr->priv;
+ MateRRScreen *screen = priv->rw_screen;
+ MateRRConfig *config;
+ gboolean turn_on_external, turn_on_laptop;
+
+ turn_on_external =
+ mateconf_client_get_bool (mgr->priv->client, CONF_KEY_TURN_ON_EXTERNAL_MONITORS_AT_STARTUP, NULL);
+ turn_on_laptop =
+ mateconf_client_get_bool (mgr->priv->client, CONF_KEY_TURN_ON_LAPTOP_MONITOR_AT_STARTUP, NULL);
+
+ if (turn_on_external && turn_on_laptop)
+ config = make_clone_setup (screen);
+ else if (!turn_on_external && turn_on_laptop)
+ config = make_laptop_setup (screen);
+ else if (turn_on_external && !turn_on_laptop)
+ config = make_other_setup (screen);
+ else
+ config = make_laptop_setup (screen);
+
+ if (config) {
+ apply_configuration_and_display_error (mgr, config, timestamp);
+ mate_rr_config_free (config);
+ }
+}
+
+static gboolean
+apply_stored_configuration_at_startup (MsdXrandrManager *manager, guint32 timestamp)
+{
+ GError *my_error;
+ gboolean success;
+ char *backup_filename;
+ char *intended_filename;
+
+ backup_filename = mate_rr_config_get_backup_filename ();
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ /* 1. See if there was a "saved" configuration. If there is one, it means
+ * that the user had selected to change the display configuration, but the
+ * machine crashed. In that case, we'll apply *that* configuration and save it on top of the
+ * "intended" one.
+ */
+
+ my_error = NULL;
+
+ success = apply_configuration_from_filename (manager, backup_filename, FALSE, timestamp, &my_error);
+ if (success) {
+ /* The backup configuration existed, and could be applied
+ * successfully, so we must restore it on top of the
+ * failed/intended one.
+ */
+ restore_backup_configuration (manager, backup_filename, intended_filename, timestamp);
+ goto out;
+ }
+
+ if (!g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+ /* Epic fail: there (probably) was a backup configuration, but
+ * we could not apply it. The only thing we can do is delete
+ * the backup configuration. Let's hope that the user doesn't
+ * get left with an unusable display...
+ */
+
+ unlink (backup_filename);
+ goto out;
+ }
+
+ /* 2. There was no backup configuration! This means we are
+ * good. Apply the intended configuration instead.
+ */
+
+ success = apply_intended_configuration (manager, intended_filename, timestamp);
+
+out:
+
+ if (my_error)
+ g_error_free (my_error);
+
+ g_free (backup_filename);
+ g_free (intended_filename);
+
+ return success;
+}
+
+static gboolean
+apply_default_configuration_from_file (MsdXrandrManager *manager, guint32 timestamp)
+{
+ MsdXrandrManagerPrivate *priv = manager->priv;
+ char *default_config_filename;
+ gboolean result;
+
+ default_config_filename = mateconf_client_get_string (priv->client, CONF_KEY_DEFAULT_CONFIGURATION_FILE, NULL);
+ if (!default_config_filename)
+ return FALSE;
+
+ result = apply_configuration_from_filename (manager, default_config_filename, TRUE, timestamp, NULL);
+
+ g_free (default_config_filename);
+ return result;
+}
+
+gboolean
+msd_xrandr_manager_start (MsdXrandrManager *manager,
+ GError **error)
+{
+ g_debug ("Starting xrandr manager");
+ mate_settings_profile_start (NULL);
+
+ log_open ();
+ log_msg ("------------------------------------------------------------\nSTARTING XRANDR PLUGIN\n");
+
+ manager->priv->rw_screen = mate_rr_screen_new (
+ gdk_screen_get_default (), on_randr_event, manager, error);
+
+ if (manager->priv->rw_screen == NULL) {
+ log_msg ("Could not initialize the RANDR plugin%s%s\n",
+ (error && *error) ? ": " : "",
+ (error && *error) ? (*error)->message : "");
+ log_close ();
+ return FALSE;
+ }
+
+ log_msg ("State of screen at startup:\n");
+ log_screen (manager->priv->rw_screen);
+
+ manager->priv->running = TRUE;
+ manager->priv->client = mateconf_client_get_default ();
+
+ g_assert (manager->priv->notify_id == 0);
+
+ mateconf_client_add_dir (manager->priv->client, CONF_DIR,
+ MATECONF_CLIENT_PRELOAD_ONELEVEL,
+ NULL);
+
+ manager->priv->notify_id =
+ mateconf_client_notify_add (
+ manager->priv->client, CONF_DIR,
+ (MateConfClientNotifyFunc)on_config_changed,
+ manager, NULL, NULL);
+
+ if (manager->priv->switch_video_mode_keycode) {
+ gdk_error_trap_push ();
+
+ XGrabKey (gdk_x11_get_default_xdisplay(),
+ manager->priv->switch_video_mode_keycode, AnyModifier,
+ gdk_x11_get_default_root_xwindow(),
+ True, GrabModeAsync, GrabModeAsync);
+
+ gdk_flush ();
+ gdk_error_trap_pop ();
+ }
+
+ if (manager->priv->rotate_windows_keycode) {
+ gdk_error_trap_push ();
+
+ XGrabKey (gdk_x11_get_default_xdisplay(),
+ manager->priv->rotate_windows_keycode, AnyModifier,
+ gdk_x11_get_default_root_xwindow(),
+ True, GrabModeAsync, GrabModeAsync);
+
+ gdk_flush ();
+ gdk_error_trap_pop ();
+ }
+
+ show_timestamps_dialog (manager, "Startup");
+ if (!apply_stored_configuration_at_startup (manager, GDK_CURRENT_TIME)) /* we don't have a real timestamp at startup anyway */
+ if (!apply_default_configuration_from_file (manager, GDK_CURRENT_TIME))
+ apply_default_boot_configuration (manager, GDK_CURRENT_TIME);
+
+ log_msg ("State of screen after initial configuration:\n");
+ log_screen (manager->priv->rw_screen);
+
+ gdk_window_add_filter (gdk_get_default_root_window(),
+ (GdkFilterFunc)event_filter,
+ manager);
+
+ start_or_stop_icon (manager);
+
+ log_close ();
+
+ mate_settings_profile_end (NULL);
+
+ return TRUE;
+}
+
+void
+msd_xrandr_manager_stop (MsdXrandrManager *manager)
+{
+ g_debug ("Stopping xrandr manager");
+
+ manager->priv->running = FALSE;
+
+ if (manager->priv->switch_video_mode_keycode) {
+ gdk_error_trap_push ();
+
+ XUngrabKey (gdk_x11_get_default_xdisplay(),
+ manager->priv->switch_video_mode_keycode, AnyModifier,
+ gdk_x11_get_default_root_xwindow());
+
+ gdk_error_trap_pop ();
+ }
+
+ if (manager->priv->rotate_windows_keycode) {
+ gdk_error_trap_push ();
+
+ XUngrabKey (gdk_x11_get_default_xdisplay(),
+ manager->priv->rotate_windows_keycode, AnyModifier,
+ gdk_x11_get_default_root_xwindow());
+
+ gdk_error_trap_pop ();
+ }
+
+ gdk_window_remove_filter (gdk_get_default_root_window (),
+ (GdkFilterFunc) event_filter,
+ manager);
+
+ if (manager->priv->notify_id != 0) {
+ mateconf_client_remove_dir (manager->priv->client,
+ CONF_DIR, NULL);
+ mateconf_client_notify_remove (manager->priv->client,
+ manager->priv->notify_id);
+ manager->priv->notify_id = 0;
+ }
+
+ if (manager->priv->client != NULL) {
+ g_object_unref (manager->priv->client);
+ manager->priv->client = NULL;
+ }
+
+ if (manager->priv->rw_screen != NULL) {
+ mate_rr_screen_destroy (manager->priv->rw_screen);
+ manager->priv->rw_screen = NULL;
+ }
+
+ if (manager->priv->dbus_connection != NULL) {
+ dbus_g_connection_unref (manager->priv->dbus_connection);
+ manager->priv->dbus_connection = NULL;
+ }
+
+ status_icon_stop (manager);
+
+ log_open ();
+ log_msg ("STOPPING XRANDR PLUGIN\n------------------------------------------------------------\n");
+ log_close ();
+}
+
+static void
+msd_xrandr_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MsdXrandrManager *self;
+
+ self = MSD_XRANDR_MANAGER (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+msd_xrandr_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MsdXrandrManager *self;
+
+ self = MSD_XRANDR_MANAGER (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+msd_xrandr_manager_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ MsdXrandrManager *xrandr_manager;
+ MsdXrandrManagerClass *klass;
+
+ klass = MSD_XRANDR_MANAGER_CLASS (g_type_class_peek (MSD_TYPE_XRANDR_MANAGER));
+
+ xrandr_manager = MSD_XRANDR_MANAGER (G_OBJECT_CLASS (msd_xrandr_manager_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ return G_OBJECT (xrandr_manager);
+}
+
+static void
+msd_xrandr_manager_dispose (GObject *object)
+{
+ MsdXrandrManager *xrandr_manager;
+
+ xrandr_manager = MSD_XRANDR_MANAGER (object);
+
+ G_OBJECT_CLASS (msd_xrandr_manager_parent_class)->dispose (object);
+}
+
+static void
+msd_xrandr_manager_class_init (MsdXrandrManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = msd_xrandr_manager_get_property;
+ object_class->set_property = msd_xrandr_manager_set_property;
+ object_class->constructor = msd_xrandr_manager_constructor;
+ object_class->dispose = msd_xrandr_manager_dispose;
+ object_class->finalize = msd_xrandr_manager_finalize;
+
+ dbus_g_object_type_install_info (MSD_TYPE_XRANDR_MANAGER, &dbus_glib_msd_xrandr_manager_object_info);
+
+ g_type_class_add_private (klass, sizeof (MsdXrandrManagerPrivate));
+}
+
+static guint
+get_keycode_for_keysym_name (const char *name)
+{
+ Display *dpy;
+ guint keyval;
+
+ dpy = gdk_x11_get_default_xdisplay ();
+
+ keyval = gdk_keyval_from_name (name);
+ return XKeysymToKeycode (dpy, keyval);
+}
+
+static void
+msd_xrandr_manager_init (MsdXrandrManager *manager)
+{
+ manager->priv = MSD_XRANDR_MANAGER_GET_PRIVATE (manager);
+
+ manager->priv->switch_video_mode_keycode = get_keycode_for_keysym_name (VIDEO_KEYSYM);
+ manager->priv->rotate_windows_keycode = get_keycode_for_keysym_name (ROTATE_KEYSYM);
+
+ manager->priv->current_fn_f7_config = -1;
+ manager->priv->fn_f7_configs = NULL;
+}
+
+static void
+msd_xrandr_manager_finalize (GObject *object)
+{
+ MsdXrandrManager *xrandr_manager;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (MSD_IS_XRANDR_MANAGER (object));
+
+ xrandr_manager = MSD_XRANDR_MANAGER (object);
+
+ g_return_if_fail (xrandr_manager->priv != NULL);
+
+ G_OBJECT_CLASS (msd_xrandr_manager_parent_class)->finalize (object);
+}
+
+static gboolean
+register_manager_dbus (MsdXrandrManager *manager)
+{
+ GError *error = NULL;
+
+ manager->priv->dbus_connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+ if (manager->priv->dbus_connection == NULL) {
+ if (error != NULL) {
+ g_warning ("Error getting session bus: %s", error->message);
+ g_error_free (error);
+ }
+ return FALSE;
+ }
+
+ /* Hmm, should we do this in msd_xrandr_manager_start()? */
+ dbus_g_connection_register_g_object (manager->priv->dbus_connection, MSD_XRANDR_DBUS_PATH, G_OBJECT (manager));
+
+ return TRUE;
+}
+
+MsdXrandrManager *
+msd_xrandr_manager_new (void)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ manager_object = g_object_new (MSD_TYPE_XRANDR_MANAGER, NULL);
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+
+ if (!register_manager_dbus (manager_object)) {
+ g_object_unref (manager_object);
+ return NULL;
+ }
+ }
+
+ return MSD_XRANDR_MANAGER (manager_object);
+}