457 lines
17 KiB
Diff
457 lines
17 KiB
Diff
diff --git a/plugins/xrandr/gsd-xrandr-manager.c b/plugins/xrandr/gsd-xrandr-manager.c
|
|
index 62010fe..4d8ce2f 100644
|
|
--- a/plugins/xrandr/gsd-xrandr-manager.c
|
|
+++ b/plugins/xrandr/gsd-xrandr-manager.c
|
|
@@ -42,6 +42,7 @@
|
|
|
|
#include <libgnomeui/gnome-rr-config.h>
|
|
#include <libgnomeui/gnome-rr.h>
|
|
+#include <libgnomeui/gnome-rr-labeler.h>
|
|
|
|
#ifdef HAVE_RANDR
|
|
#include <X11/extensions/Xrandr.h>
|
|
@@ -76,6 +77,9 @@ struct GsdXrandrManagerPrivate
|
|
gboolean client_filter_set;
|
|
|
|
GtkStatusIcon *status_icon;
|
|
+ GtkWidget *popup_menu;
|
|
+ GnomeRRConfig *configuration;
|
|
+ GnomeRRLabeler *labeler;
|
|
GConfClient *client;
|
|
int notify_id;
|
|
};
|
|
@@ -186,30 +190,423 @@ popup_menu_configure_display_cb (GtkMenuItem *item, gpointer data)
|
|
}
|
|
|
|
static void
|
|
+status_icon_popup_menu_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
|
|
+{
|
|
+ GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
|
|
+ struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
+
|
|
+ gtk_widget_destroy (priv->popup_menu);
|
|
+ priv->popup_menu = NULL;
|
|
+
|
|
+ gnome_rr_labeler_hide (priv->labeler);
|
|
+ g_object_unref (priv->labeler);
|
|
+ priv->labeler = NULL;
|
|
+
|
|
+ gnome_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 GnomeRROutput.
|
|
+ * 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)
|
|
+{
|
|
+ GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
|
|
+ struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
+ GnomeOutputInfo *output;
|
|
+ GdkColor color;
|
|
+ cairo_t *cr;
|
|
+
|
|
+ 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 */
|
|
+
|
|
+ gnome_rr_labeler_get_color_for_output (priv->labeler, output, &color);
|
|
+
|
|
+ cr = gdk_cairo_create (widget->window);
|
|
+
|
|
+ cairo_set_source_rgb (cr, 0, 0, 0);
|
|
+ cairo_set_line_width (cr, OUTPUT_TITLE_ITEM_BORDER);
|
|
+ cairo_rectangle (cr,
|
|
+ widget->allocation.x + OUTPUT_TITLE_ITEM_BORDER / 2.0,
|
|
+ widget->allocation.y + OUTPUT_TITLE_ITEM_BORDER / 2.0,
|
|
+ widget->allocation.width - OUTPUT_TITLE_ITEM_BORDER,
|
|
+ widget->allocation.height - OUTPUT_TITLE_ITEM_BORDER);
|
|
+ cairo_stroke (cr);
|
|
+
|
|
+ gdk_cairo_set_source_color (cr, &color);
|
|
+ cairo_rectangle (cr,
|
|
+ widget->allocation.x + OUTPUT_TITLE_ITEM_BORDER,
|
|
+ widget->allocation.y + OUTPUT_TITLE_ITEM_BORDER,
|
|
+ widget->allocation.width - 2 * OUTPUT_TITLE_ITEM_BORDER,
|
|
+ widget->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.
|
|
+ */
|
|
+
|
|
+ widget->state = 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));
|
|
+ widget->state = 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 (GsdXrandrManager *manager, GnomeOutputInfo *output)
|
|
+{
|
|
+ GtkWidget *item;
|
|
+ GtkWidget *label;
|
|
+ char *str;
|
|
+
|
|
+ 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);
|
|
+
|
|
+ /* 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 (GsdXrandrManager *manager, GnomeOutputInfo *output, int *out_num_rotations, GnomeRRRotation *out_rotations)
|
|
+{
|
|
+ static const GnomeRRRotation possible_rotations[] = {
|
|
+ GNOME_RR_ROTATION_0,
|
|
+ GNOME_RR_ROTATION_90,
|
|
+ GNOME_RR_ROTATION_180,
|
|
+ GNOME_RR_ROTATION_270
|
|
+ /* We don't allow REFLECT_X or REFLECT_Y for now, as gnome-display-properties doesn't allow them, either */
|
|
+ };
|
|
+
|
|
+ struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
+ GnomeRRRotation 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++) {
|
|
+ GnomeRRRotation rotation_to_test;
|
|
+
|
|
+ rotation_to_test = possible_rotations[i];
|
|
+
|
|
+ output->rotation = rotation_to_test;
|
|
+
|
|
+ if (gnome_rr_config_applicable (priv->configuration, priv->rw_screen)) {
|
|
+ (*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 (GsdXrandrManager *manager)
|
|
+{
|
|
+ struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
+ GtkWidget *item;
|
|
+ GtkWidget *label;
|
|
+
|
|
+ item = gtk_menu_item_new ();
|
|
+
|
|
+ label = gtk_label_new (NULL);
|
|
+ gtk_label_set_markup (GTK_LABEL (label), _("<i>Rotation not supported</i>"));
|
|
+ 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
|
|
+error_dialog (const char *title, const char *msg)
|
|
+{
|
|
+ GtkWidget *dialog;
|
|
+
|
|
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
|
+ "%s", title);
|
|
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", msg);
|
|
+
|
|
+ gtk_dialog_run (GTK_DIALOG (dialog));
|
|
+ gtk_widget_destroy (dialog);
|
|
+}
|
|
+
|
|
+static void
|
|
+output_rotation_item_activate_cb (GtkMenuItem *item, gpointer data)
|
|
+{
|
|
+ GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
|
|
+ struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
+ GnomeOutputInfo *output;
|
|
+ GnomeRRRotation rotation;
|
|
+ GError *error;
|
|
+
|
|
+ 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 (gnome_rr_config_save (priv->configuration, &error)) {
|
|
+ if (!gnome_rr_config_apply_stored (priv->rw_screen)) {
|
|
+ error_dialog (_("The selected rotation could not be applied"),
|
|
+ _("An error occurred while configuring the screen"));
|
|
+ /* FIXME: that message is really useless. Make
|
|
+ * gnome_rr_config_apply_stored() give us a meaningful
|
|
+ * error message!
|
|
+ */
|
|
+ }
|
|
+ } else {
|
|
+ error_dialog (_("The selected rotation could not be applied"),
|
|
+ error->message);
|
|
+ g_error_free (error);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+add_items_for_rotations (GsdXrandrManager *manager, GnomeOutputInfo *output, GnomeRRRotation allowed_rotations)
|
|
+{
|
|
+ typedef struct {
|
|
+ GnomeRRRotation rotation;
|
|
+ const char * name;
|
|
+ } RotationInfo;
|
|
+ static const RotationInfo rotations[] = {
|
|
+ { GNOME_RR_ROTATION_0, N_("Normal") },
|
|
+ { GNOME_RR_ROTATION_90, N_("Left") },
|
|
+ { GNOME_RR_ROTATION_270, N_("Right") },
|
|
+ { GNOME_RR_ROTATION_180, N_("Upside Down") },
|
|
+ /* We don't allow REFLECT_X or REFLECT_Y for now, as gnome-display-properties doesn't allow them, either */
|
|
+ };
|
|
+
|
|
+ struct GsdXrandrManagerPrivate *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++) {
|
|
+ GnomeRRRotation 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 (GsdXrandrManager *manager, GnomeOutputInfo *output)
|
|
+{
|
|
+ int num_rotations;
|
|
+ GnomeRRRotation rotations;
|
|
+
|
|
+ get_allowed_rotations_for_output (manager, 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 (GsdXrandrManager *manager, GnomeOutputInfo *output)
|
|
+{
|
|
+ struct GsdXrandrManagerPrivate *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 (GsdXrandrManager *manager)
|
|
+{
|
|
+ struct GsdXrandrManagerPrivate *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 (GsdXrandrManager *manager, guint button, guint32 timestamp)
|
|
{
|
|
struct GsdXrandrManagerPrivate *priv = manager->priv;
|
|
- GtkWidget *menu;
|
|
GtkWidget *item;
|
|
|
|
- menu = gtk_menu_new ();
|
|
+ g_assert (priv->configuration == NULL);
|
|
+ priv->configuration = gnome_rr_config_new_current (priv->rw_screen);
|
|
+
|
|
+ g_assert (priv->labeler == NULL);
|
|
+ priv->labeler = gnome_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_menu_item_new_with_label (_("Screen Rotation"));
|
|
- gtk_widget_set_sensitive (item, FALSE);
|
|
+ item = gtk_separator_menu_item_new ();
|
|
gtk_widget_show (item);
|
|
- gtk_menu_shell_append (GTK_MENU_SHELL (menu), 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 (menu), item);
|
|
- /* FIXME */
|
|
+ gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
|
|
|
|
- g_signal_connect (menu, "selection-done",
|
|
- G_CALLBACK (gtk_widget_destroy), NULL);
|
|
+ g_signal_connect (priv->popup_menu, "selection-done",
|
|
+ G_CALLBACK (status_icon_popup_menu_selection_done_cb), manager);
|
|
|
|
- gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
|
|
+ gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
|
|
gtk_status_icon_position_menu,
|
|
priv->status_icon, button, timestamp);
|
|
}
|