gnome-control-center/panels/display/cc-display-panel.c
Rui Matos 9b0cf3391c display: Refactor night light widget instantiation
We'll need multiple instances of this widget in the new panel
design. This makes it possible.

https://bugzilla.gnome.org/show_bug.cgi?id=785949
2017-08-09 19:27:08 +02:00

2991 lines
90 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2007, 2008 Red Hat, Inc.
* Copyright (C) 2013 Intel, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include "cc-display-panel.h"
#include <gtk/gtk.h>
#include "scrollarea.h"
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-bg.h>
#include <glib/gi18n.h>
#include <stdlib.h>
#include <gdesktop-enums.h>
#include <math.h>
#include "shell/list-box-helper.h"
#include <libupower-glib/upower.h>
#include "cc-display-config-manager-rr.h"
#include "cc-display-config-manager-dbus.h"
#include "cc-display-config.h"
#include "cc-night-light-dialog.h"
#include "cc-display-resources.h"
CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel)
#define DISPLAY_PANEL_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_DISPLAY_PANEL, CcDisplayPanelPrivate))
#define WID(s) GTK_WIDGET (gtk_builder_get_object (self->priv->builder, s))
#define TOP_BAR_HEIGHT 5
/* The minimum supported size for the panel, see:
* http://live.gnome.org/Design/SystemSettings */
#define MINIMUM_WIDTH 740
#define MINIMUM_HEIGHT 530
#define DISPLAY_PREVIEW_SETUP_HEIGHT 140
#define DISPLAY_PREVIEW_LIST_HEIGHT 55
enum
{
DISPLAY_MODE_PRIMARY,
DISPLAY_MODE_SECONDARY,
/* DISPLAY_MODE_PRESENTATION, */
DISPLAY_MODE_MIRROR,
DISPLAY_MODE_OFF
};
struct _CcDisplayPanelPrivate
{
gboolean have_new_dbus_api;
CcDisplayConfigManager *manager;
CcDisplayConfig *current_config;
CcDisplayMonitor *current_output;
GnomeBG *background;
GnomeDesktopThumbnailFactory *thumbnail_factory;
guint focus_id;
GtkWidget *stack;
GtkWidget *displays_listbox;
GtkWidget *arrange_button;
GtkWidget *res_combo;
GtkWidget *freq_combo;
GtkWidget *scale_slider;
GHashTable *res_freqs;
GtkWidget *scaling_switch;
GtkWidget *rotate_left_button;
GtkWidget *upside_down_button;
GtkWidget *rotate_right_button;
GtkWidget *dialog;
GtkWidget *config_grid;
CcNightLightDialog *night_light_dialog;
GSettings *settings_color;
UpClient *up_client;
gboolean lid_is_closed;
GDBusProxy *shell_proxy;
GCancellable *shell_cancellable;
guint sensor_watch_id;
GDBusProxy *iio_sensor_proxy;
gboolean has_accelerometer;
};
typedef struct
{
int grab_x;
int grab_y;
int output_x;
int output_y;
} GrabInfo;
static GHashTable *output_ids;
static gint
cc_display_panel_get_output_id (CcDisplayMonitor *output)
{
if (output_ids)
return GPOINTER_TO_INT (g_hash_table_lookup (output_ids, output));
else
return 0;
}
static void
monitor_labeler_show (CcDisplayPanel *self)
{
CcDisplayPanelPrivate *priv = self->priv;
GList *outputs, *l;
GVariantBuilder builder;
gint number;
gboolean has_outputs;
if (!priv->shell_proxy || !priv->current_config)
return;
has_outputs = FALSE;
outputs = cc_display_config_get_monitors (priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
number = cc_display_panel_get_output_id (output);
if (number == 0)
continue;
if (!has_outputs)
{
g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
has_outputs = TRUE;
}
if (priv->have_new_dbus_api)
g_variant_builder_add (&builder, "{sv}",
cc_display_monitor_get_connector_name (output),
g_variant_new_int32 (number));
else
g_variant_builder_add (&builder, "{uv}",
cc_display_monitor_get_id (output),
g_variant_new_int32 (number));
}
if (!has_outputs)
return;
g_variant_builder_close (&builder);
g_dbus_proxy_call (priv->shell_proxy,
priv->have_new_dbus_api ? "ShowMonitorLabels2" : "ShowMonitorLabels",
g_variant_builder_end (&builder),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL, NULL);
}
static void
monitor_labeler_hide (CcDisplayPanel *self)
{
CcDisplayPanelPrivate *priv = self->priv;
if (!priv->shell_proxy)
return;
g_dbus_proxy_call (priv->shell_proxy,
"HideMonitorLabels",
NULL, G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL, NULL);
}
static void
ensure_monitor_labels (CcDisplayPanel *self)
{
GList *windows, *w;
windows = gtk_window_list_toplevels ();
for (w = windows; w; w = w->next)
{
if (gtk_window_has_toplevel_focus (GTK_WINDOW (w->data)))
{
monitor_labeler_show (self);
break;
}
}
if (!w)
monitor_labeler_hide (self);
g_list_free (windows);
}
static void
cc_display_panel_dispose (GObject *object)
{
CcDisplayPanelPrivate *priv = CC_DISPLAY_PANEL (object)->priv;
CcShell *shell;
GtkWidget *toplevel;
if (priv->sensor_watch_id > 0)
{
g_bus_unwatch_name (priv->sensor_watch_id);
priv->sensor_watch_id = 0;
}
g_clear_object (&priv->iio_sensor_proxy);
if (output_ids)
{
g_hash_table_destroy (output_ids);
output_ids = NULL;
}
if (priv->focus_id)
{
shell = cc_panel_get_shell (CC_PANEL (object));
toplevel = cc_shell_get_toplevel (shell);
if (toplevel != NULL)
g_signal_handler_disconnect (G_OBJECT (toplevel),
priv->focus_id);
priv->focus_id = 0;
monitor_labeler_hide (CC_DISPLAY_PANEL (object));
}
g_clear_object (&priv->manager);
g_clear_object (&priv->current_config);
g_clear_object (&priv->up_client);
g_clear_object (&priv->background);
g_clear_object (&priv->thumbnail_factory);
g_clear_object (&priv->settings_color);
g_clear_object (&priv->night_light_dialog);
if (priv->dialog)
{
gtk_widget_destroy (priv->dialog);
priv->dialog = NULL;
}
g_cancellable_cancel (priv->shell_cancellable);
g_clear_object (&priv->shell_cancellable);
g_clear_object (&priv->shell_proxy);
G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object);
}
static const char *
cc_display_panel_get_help_uri (CcPanel *panel)
{
return "help:gnome-help/prefs-display";
}
static void
cc_display_panel_class_init (CcDisplayPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
g_type_class_add_private (klass, sizeof (CcDisplayPanelPrivate));
panel_class->get_help_uri = cc_display_panel_get_help_uri;
object_class->dispose = cc_display_panel_dispose;
}
static gboolean
should_show_resolution (gint output_width,
gint output_height,
gint width,
gint height)
{
if (width >= MIN (output_width, MINIMUM_WIDTH) &&
height >= MIN (output_height, MINIMUM_HEIGHT))
{
return TRUE;
}
return FALSE;
}
static void
apply_rotation_to_geometry (CcDisplayMonitor *output, int *w, int *h)
{
CcDisplayRotation rotation;
rotation = cc_display_monitor_get_rotation (output);
if ((rotation == CC_DISPLAY_ROTATION_90) || (rotation == CC_DISPLAY_ROTATION_270))
{
int tmp;
tmp = *h;
*h = *w;
*w = tmp;
}
}
static void
get_geometry (CcDisplayMonitor *output, int *x, int *y, int *w, int *h)
{
if (cc_display_monitor_is_active (output))
{
cc_display_monitor_get_geometry (output, x, y, w, h);
}
else
{
cc_display_monitor_get_geometry (output, x, y, NULL, NULL);
cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (output),
w, h);
}
apply_rotation_to_geometry (output, w, h);
}
static void
on_viewport_changed (FooScrollArea *scroll_area,
GdkRectangle *old_viewport,
GdkRectangle *new_viewport)
{
foo_scroll_area_set_size (scroll_area,
new_viewport->width,
new_viewport->height);
foo_scroll_area_invalidate (scroll_area);
}
static void
paint_output (CcDisplayPanel *panel,
cairo_t *cr,
CcDisplayConfig *configuration,
CcDisplayMonitor *output,
gint num,
gint allocated_width,
gint allocated_height)
{
GdkPixbuf *pixbuf;
gint x, y, width, height;
get_geometry (output, NULL, NULL, &width, &height);
x = y = 0;
/* scale to fit allocation */
if (width / (double) height < allocated_width / (double) allocated_height)
{
width = allocated_height * (width / (double) height);
height = allocated_height;
}
else
{
height = allocated_width * (height / (double) width);
width = allocated_width;
}
x = (allocated_width / 2.0) - (width / 2.0);
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_rectangle (cr, x, y, width, height);
cairo_fill (cr);
if (cc_display_monitor_is_active (output))
{
pixbuf = gnome_bg_create_thumbnail (panel->priv->background,
panel->priv->thumbnail_factory,
gdk_screen_get_default (), width, height);
}
else
pixbuf = NULL;
if (cc_display_monitor_is_primary (output)
|| cc_display_config_is_cloning (configuration))
{
y += TOP_BAR_HEIGHT;
height -= TOP_BAR_HEIGHT;
}
if (pixbuf)
gdk_cairo_set_source_pixbuf (cr, pixbuf, x + 1, y + 1);
else
cairo_set_source_rgb (cr, 0.3, 0.3, 0.3);
cairo_rectangle (cr, x + 1, y + 1, width - 2, height - 2);
cairo_fill (cr);
g_clear_object (&pixbuf);
if (num > 0)
{
PangoLayout *layout;
gchar *number_str;
gdouble r = 3, r2 = r / 2.0, x1, y1, x2, y2;
PangoRectangle extents;
gdouble max_extent;
number_str = g_strdup_printf ("<small>%d</small>", num);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (panel), "");
pango_layout_set_markup (layout, number_str, -1);
pango_layout_get_extents (layout, NULL, &extents);
g_free (number_str);
cairo_set_source_rgba (cr, 0, 0, 0, 0.75);
max_extent = MAX ((extents.width - extents.x)/ PANGO_SCALE,
(extents.height - extents.y) / PANGO_SCALE);
x += 5;
y += 5;
x1 = x;
x2 = x1 + max_extent + 1;
y1 = y;
y2 = y1 + max_extent + 1;
cairo_move_to (cr, x1 + r, y1);
cairo_line_to (cr, x2 - r, y1);
cairo_curve_to (cr, x2 - r2, y1, x2, y1 + r2, x2, y1 + r);
cairo_line_to (cr, x2, y2 - r);
cairo_curve_to (cr, x2, y2 - r2, x2 - r2, y2, x2 - r, y2);
cairo_line_to (cr, x1 + r, y2);
cairo_curve_to (cr, x1 + r2, y2, x1, y2 - r2, x1, y2 - r);
cairo_line_to (cr, x1, y1 + r);
cairo_curve_to (cr, x1, y1 + r2, x1 + r2, y1, x1 + r, y1);
cairo_fill (cr);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_move_to (cr,
x + (max_extent / 2.0) - ((extents.width / PANGO_SCALE) / 2.0),
y + (max_extent / 2.0) - ((extents.height / PANGO_SCALE) / 2.0));
pango_cairo_show_layout (cr, layout);
cairo_fill (cr);
g_object_unref (layout);
}
}
static gboolean
display_preview_draw (GtkWidget *widget,
cairo_t *cr,
CcDisplayPanel *panel)
{
CcDisplayMonitor *output;
CcDisplayConfig *config;
gint num, width, height;
output = g_object_get_data (G_OBJECT (widget), "output");
config = g_object_get_data (G_OBJECT (widget), "config");
num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "number"));
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
paint_output (panel, cr, config, output, num, width, height);
return TRUE;
}
static GtkWidget*
display_preview_new (CcDisplayPanel *panel,
CcDisplayMonitor *output,
CcDisplayConfig *config,
gint num,
gint base_height)
{
GtkWidget *area;
gint width, height;
get_geometry (output, NULL, NULL, &width, &height);
area = gtk_drawing_area_new ();
g_signal_connect (area, "draw", G_CALLBACK (display_preview_draw), panel);
gtk_widget_set_size_request (area, base_height * (width / (gdouble) height), base_height);
gtk_widget_set_valign (area, GTK_ALIGN_CENTER);
gtk_widget_set_halign (area, GTK_ALIGN_CENTER);
g_object_set_data (G_OBJECT (area), "output", output);
g_object_set_data (G_OBJECT (area), "config", config);
g_object_set_data (G_OBJECT (area), "number", GINT_TO_POINTER (num));
return area;
}
static void
on_screen_changed (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayConfig *current;
GList *outputs;
gint num_connected_outputs = 0, number = 0;
gboolean clone = FALSE, combined = FALSE;
GtkSizeGroup *sizegroup;
GList *sorted_outputs = NULL, *l;
if (priv->dialog)
gtk_dialog_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_NONE);
g_clear_object (&priv->current_config);
current = cc_display_config_manager_get_current (priv->manager);
if (!current)
{
gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "error");
return;
}
priv->current_config = current;
gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "main");
gtk_container_foreach (GTK_CONTAINER (priv->displays_listbox),
(GtkCallback) gtk_widget_destroy, NULL);
outputs = cc_display_config_get_monitors (current);
/* count the number of active and connected outputs */
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
/* ensure the built in display is first in the list */
if (cc_display_monitor_is_builtin (output))
sorted_outputs = g_list_prepend (sorted_outputs, output);
else
sorted_outputs = g_list_append (sorted_outputs, output);
num_connected_outputs++;
}
sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
g_hash_table_remove_all (output_ids);
for (l = sorted_outputs; l; l = g_list_next (l))
{
GtkWidget *row, *item, *preview, *label;
gboolean primary, active;
const gchar *status;
gboolean display_closed = FALSE;
CcDisplayMonitor *output = l->data;
if (priv->lid_is_closed)
display_closed = cc_display_monitor_is_builtin (output);
row = gtk_list_box_row_new ();
item = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_container_set_border_width (GTK_CONTAINER (item), 12);
preview = display_preview_new (panel, output, current, ++number,
DISPLAY_PREVIEW_LIST_HEIGHT);
gtk_size_group_add_widget (sizegroup, preview);
if (display_closed)
gtk_widget_set_sensitive (row, FALSE);
g_hash_table_insert (output_ids, output, GINT_TO_POINTER (number));
gtk_container_add (GTK_CONTAINER (item), preview);
label = gtk_label_new (cc_display_monitor_get_display_name (output));
gtk_container_add (GTK_CONTAINER (item), label);
primary = cc_display_monitor_is_primary (output);
active = cc_display_monitor_is_active (output);
if (num_connected_outputs > 1)
{
if (display_closed)
status = _("Lid Closed");
else if (clone)
/* translators: "Mirrored" describes when both displays show the same view */
status = _("Mirrored");
else if (primary)
status = _("Primary");
else if (!active)
status = _("Off");
else
{
status = _("Secondary");
combined = TRUE;
}
label = gtk_label_new (status);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_container_add (GTK_CONTAINER (item), label);
}
g_object_set_data (G_OBJECT (row), "cc-display-monitor", output);
gtk_container_add (GTK_CONTAINER (row), item);
gtk_container_add (GTK_CONTAINER (priv->displays_listbox), row);
gtk_widget_show_all (row);
}
g_list_free (sorted_outputs);
if (combined)
gtk_widget_show (priv->arrange_button);
else
gtk_widget_hide (priv->arrange_button);
ensure_monitor_labels (panel);
}
static void
realign_outputs_after_resolution_change (CcDisplayPanel *self, CcDisplayMonitor *output_that_changed, int old_width, int old_height, CcDisplayRotation old_rotation)
{
/* We find the outputs that were below or to the right of the output that
* changed, and realign them; we also do that for outputs that shared the
* right/bottom edges with the output that changed. The outputs that are
* above or to the left of that output don't need to change.
*/
int old_right_edge, old_bottom_edge;
int dx, dy;
int x, y, width, height;
GList *outputs, *l;
CcDisplayRotation rotation;
g_assert (self->priv->current_config != NULL);
cc_display_monitor_get_geometry (output_that_changed, &x, &y, &width, &height);
rotation = cc_display_monitor_get_rotation (output_that_changed);
if (width == old_width && height == old_height && rotation == old_rotation)
{
g_debug ("Not realigning outputs, configuration is the same for %s", cc_display_monitor_get_display_name (output_that_changed));
return;
}
g_debug ("Realigning outputs, configuration changed for %s", cc_display_monitor_get_display_name (output_that_changed));
/* Apply rotation to the geometry of the newly changed output,
* as well as to its old configuration */
apply_rotation_to_geometry (output_that_changed, &width, &height);
if ((old_rotation == CC_DISPLAY_ROTATION_90) || (old_rotation == CC_DISPLAY_ROTATION_270))
{
int tmp;
tmp = old_height;
old_height = old_width;
old_width = tmp;
}
old_right_edge = x + old_width;
old_bottom_edge = y + old_height;
dx = width - old_width;
dy = height - old_height;
outputs = cc_display_config_get_monitors (self->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
int output_x, output_y;
int output_width, output_height;
if (output == output_that_changed)
continue;
cc_display_monitor_get_geometry (output, &output_x, &output_y, &output_width, &output_height);
if (output_x >= old_right_edge)
output_x += dx;
else if (output_x + output_width == old_right_edge)
output_x = x + width - output_width;
if (output_y >= old_bottom_edge)
output_y += dy;
else if (output_y + output_height == old_bottom_edge)
output_y = y + height - output_height;
g_debug ("Setting geometry for %s: %dx%d+%d+%d", cc_display_monitor_get_display_name (output), output_width, output_height, output_x, output_y);
cc_display_monitor_set_position (output, output_x, output_y);
}
}
static void
lay_out_outputs_horizontally (CcDisplayPanel *self)
{
int x;
GList *outputs, *l;
/* Lay out all the monitors horizontally when "mirror screens" is turned
* off, to avoid having all of them overlapped initially. We put the
* outputs turned off on the right-hand side.
*/
x = 0;
/* First pass, all "on" outputs */
outputs = cc_display_config_get_monitors (self->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
int width;
if (cc_display_monitor_is_active (output))
{
cc_display_mode_get_resolution (cc_display_monitor_get_mode (output),
&width, NULL);
cc_display_monitor_set_position (output, x, 0);
x += width;
}
}
/* Second pass, all the black screens */
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
int width;
if (!cc_display_monitor_is_active (output))
{
cc_display_mode_get_resolution (cc_display_monitor_get_mode (output),
&width, NULL);
cc_display_monitor_set_position (output, x, 0);
x += width;
}
}
}
#define SPACE 15
#define MARGIN 15
static void
get_total_size (CcDisplayPanel *self, int *total_w, int *total_h)
{
GList *outputs, *l;
*total_w = 0;
*total_h = 0;
outputs = cc_display_config_get_monitors (self->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
int w, h;
get_geometry (output, NULL, NULL, &w, &h);
if (cc_display_config_is_layout_logical (self->priv->current_config))
{
double scale = cc_display_monitor_get_scale (output);
w /= scale;
h /= scale;
}
*total_w += w;
*total_h += h;
}
}
static double
compute_scale (CcDisplayPanel *self, FooScrollArea *area)
{
int available_w, available_h;
int total_w, total_h;
int n_monitors;
GdkRectangle viewport;
foo_scroll_area_get_viewport (area, &viewport);
get_total_size (self, &total_w, &total_h);
n_monitors = g_list_length (cc_display_config_get_monitors (self->priv->current_config));
available_w = viewport.width - 2 * MARGIN - (n_monitors - 1) * SPACE;
available_h = viewport.height - 2 * MARGIN - (n_monitors - 1) * SPACE;
return MIN ((double)available_w / total_w, (double)available_h / total_h);
}
typedef struct Edge
{
CcDisplayMonitor *output;
int x1, y1;
int x2, y2;
} Edge;
typedef struct Snap
{
Edge *snapper; /* Edge that should be snapped */
Edge *snappee;
int dy, dx;
} Snap;
static void
add_edge (CcDisplayMonitor *output, int x1, int y1, int x2, int y2, GArray *edges)
{
Edge e;
e.x1 = x1;
e.x2 = x2;
e.y1 = y1;
e.y2 = y2;
e.output = output;
g_array_append_val (edges, e);
}
static void
list_edges_for_output (CcDisplayMonitor *output, GArray *edges, gboolean should_scale)
{
int x, y, w, h;
get_geometry (output, &x, &y, &w, &h);
if (should_scale)
{
double scale = cc_display_monitor_get_scale (output);
w /= scale;
h /= scale;
}
/* Top, Bottom, Left, Right */
add_edge (output, x, y, x + w, y, edges);
add_edge (output, x, y + h, x + w, y + h, edges);
add_edge (output, x, y, x, y + h, edges);
add_edge (output, x + w, y, x + w, y + h, edges);
}
static void
list_edges (CcDisplayPanel *panel, GArray *edges)
{
GList *outputs, *l;
gboolean should_scale;
should_scale = cc_display_config_is_layout_logical (panel->priv->current_config);
outputs = cc_display_config_get_monitors (panel->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
list_edges_for_output (output, edges, should_scale);
}
}
static gboolean
overlap (int s1, int e1, int s2, int e2)
{
return (!(e1 < s2 || s1 >= e2));
}
static gboolean
horizontal_overlap (Edge *snapper, Edge *snappee)
{
if (snapper->y1 != snapper->y2 || snappee->y1 != snappee->y2)
return FALSE;
return overlap (snapper->x1, snapper->x2, snappee->x1, snappee->x2);
}
static gboolean
vertical_overlap (Edge *snapper, Edge *snappee)
{
if (snapper->x1 != snapper->x2 || snappee->x1 != snappee->x2)
return FALSE;
return overlap (snapper->y1, snapper->y2, snappee->y1, snappee->y2);
}
static void
add_snap (GArray *snaps, Snap snap)
{
if (ABS (snap.dx) <= 200 || ABS (snap.dy) <= 200)
g_array_append_val (snaps, snap);
}
static void
add_edge_snaps (Edge *snapper, Edge *snappee, GArray *snaps)
{
Snap snap;
snap.snapper = snapper;
snap.snappee = snappee;
if (horizontal_overlap (snapper, snappee))
{
snap.dx = 0;
snap.dy = snappee->y1 - snapper->y1;
add_snap (snaps, snap);
}
else if (vertical_overlap (snapper, snappee))
{
snap.dy = 0;
snap.dx = snappee->x1 - snapper->x1;
add_snap (snaps, snap);
}
/* Corner snaps */
/* 1->1 */
snap.dx = snappee->x1 - snapper->x1;
snap.dy = snappee->y1 - snapper->y1;
add_snap (snaps, snap);
/* 1->2 */
snap.dx = snappee->x2 - snapper->x1;
snap.dy = snappee->y2 - snapper->y1;
add_snap (snaps, snap);
/* 2->2 */
snap.dx = snappee->x2 - snapper->x2;
snap.dy = snappee->y2 - snapper->y2;
add_snap (snaps, snap);
/* 2->1 */
snap.dx = snappee->x1 - snapper->x2;
snap.dy = snappee->y1 - snapper->y2;
add_snap (snaps, snap);
}
static void
list_snaps (CcDisplayMonitor *output, GArray *edges, GArray *snaps)
{
int i;
for (i = 0; i < edges->len; ++i)
{
Edge *output_edge = &(g_array_index (edges, Edge, i));
if (output_edge->output == output)
{
int j;
for (j = 0; j < edges->len; ++j)
{
Edge *edge = &(g_array_index (edges, Edge, j));
if (edge->output != output)
add_edge_snaps (output_edge, edge, snaps);
}
}
}
}
#if 0
static void
print_edge (Edge *edge)
{
g_debug ("(%d %d %d %d)", edge->x1, edge->y1, edge->x2, edge->y2);
}
#endif
static gboolean
corner_on_edge (int x, int y, Edge *e)
{
if (x == e->x1 && x == e->x2 && y >= e->y1 && y <= e->y2)
return TRUE;
if (y == e->y1 && y == e->y2 && x >= e->x1 && x <= e->x2)
return TRUE;
return FALSE;
}
static gboolean
edges_align (Edge *e1, Edge *e2)
{
if (corner_on_edge (e1->x1, e1->y1, e2))
return TRUE;
if (corner_on_edge (e2->x1, e2->y1, e1))
return TRUE;
return FALSE;
}
static gboolean
output_is_aligned (CcDisplayMonitor *output, GArray *edges)
{
gboolean result = FALSE;
int i;
for (i = 0; i < edges->len; ++i)
{
Edge *output_edge = &(g_array_index (edges, Edge, i));
if (output_edge->output == output)
{
int j;
for (j = 0; j < edges->len; ++j)
{
Edge *edge = &(g_array_index (edges, Edge, j));
/* We are aligned if an output edge matches
* an edge of another output
*/
if (edge->output != output_edge->output)
{
if (edges_align (output_edge, edge))
{
result = TRUE;
goto done;
}
}
}
}
}
done:
return result;
}
static void
get_output_rect (CcDisplayMonitor *output, GdkRectangle *rect, gboolean should_scale)
{
get_geometry (output, &rect->x, &rect->y, &rect->width, &rect->height);
if (should_scale)
{
double scale = cc_display_monitor_get_scale (output);
rect->height /= scale;
rect->width /= scale;
}
}
static gboolean
output_overlaps (CcDisplayMonitor *output, CcDisplayPanel *panel)
{
GdkRectangle output_rect;
GList *outputs, *l;
gboolean should_scale;
g_assert (output != NULL);
should_scale = cc_display_config_is_layout_logical (panel->priv->current_config);
get_output_rect (output, &output_rect, should_scale);
outputs = cc_display_config_get_monitors (panel->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *o = l->data;
if (o != output)
{
GdkRectangle other_rect;
get_output_rect (o, &other_rect, should_scale);
if (gdk_rectangle_intersect (&output_rect, &other_rect, NULL))
return TRUE;
}
}
return FALSE;
}
static gboolean
config_is_aligned (CcDisplayPanel *panel, GArray *edges)
{
gboolean result = TRUE;
GList *outputs, *l;
outputs = cc_display_config_get_monitors (panel->priv->current_config);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
if (!output_is_aligned (output, edges))
return FALSE;
if (output_overlaps (output, panel))
return FALSE;
}
return result;
}
static gboolean
is_corner_snap (const Snap *s)
{
return s->dx != 0 && s->dy != 0;
}
static int
compare_snaps (gconstpointer v1, gconstpointer v2)
{
const Snap *s1 = v1;
const Snap *s2 = v2;
int sv1 = MAX (ABS (s1->dx), ABS (s1->dy));
int sv2 = MAX (ABS (s2->dx), ABS (s2->dy));
int d;
d = sv1 - sv2;
/* This snapping algorithm is good enough for rock'n'roll, but
* this is probably a better:
*
* First do a horizontal/vertical snap, then
* with the new coordinates from that snap,
* do a corner snap.
*
* Right now, it's confusing that corner snapping
* depends on the distance in an axis that you can't actually see.
*
*/
if (d == 0)
{
if (is_corner_snap (s1) && !is_corner_snap (s2))
return -1;
else if (is_corner_snap (s2) && !is_corner_snap (s1))
return 1;
else
return 0;
}
else
{
return d;
}
}
/* Sets a mouse cursor for a widget's window. As a hack, you can pass
* GDK_BLANK_CURSOR to mean "set the cursor to NULL" (i.e. reset the widget's
* window's cursor to its default).
*/
static void
set_cursor (GtkWidget *widget, GdkCursorType type)
{
GdkCursor *cursor;
GdkWindow *window;
if (type == GDK_BLANK_CURSOR)
cursor = NULL;
else
cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), type);
window = gtk_widget_get_window (widget);
if (window)
gdk_window_set_cursor (window, cursor);
if (cursor)
g_object_unref (cursor);
}
static void
grab_weak_ref_notify (gpointer area,
GObject *object)
{
foo_scroll_area_end_grab (area, NULL);
}
static void
update_apply_button (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
gboolean config_equal;
CcDisplayConfig *applied_config;
if (!cc_display_config_is_applicable (priv->current_config))
{
gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT, FALSE);
return;
}
applied_config = cc_display_config_manager_get_current (priv->manager);
config_equal = cc_display_config_equal (priv->current_config,
applied_config);
g_object_unref (applied_config);
gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT, !config_equal);
}
static void
on_output_event (FooScrollArea *area,
FooScrollAreaEvent *event,
gpointer data)
{
CcDisplayMonitor *output = data;
CcDisplayPanel *self = g_object_get_data (G_OBJECT (area), "panel");
int n_monitors;
if (event->type == FOO_DRAG_HOVER)
{
return;
}
if (event->type == FOO_DROP)
{
/* Activate new primary? */
return;
}
n_monitors = g_list_length (cc_display_config_get_monitors (self->priv->current_config));
/* If the mouse is inside the outputs, set the cursor to "you can move me". See
* on_canvas_event() for where we reset the cursor to the default if it
* exits the outputs' area.
*/
if (!cc_display_config_is_cloning (self->priv->current_config) &&
n_monitors > 1)
set_cursor (GTK_WIDGET (area), GDK_FLEUR);
if (event->type == FOO_BUTTON_PRESS)
{
GrabInfo *info;
self->priv->current_output = output;
if (!cc_display_config_is_cloning (self->priv->current_config) &&
n_monitors > 1)
{
int output_x, output_y;
cc_display_monitor_get_geometry (output, &output_x, &output_y, NULL, NULL);
foo_scroll_area_begin_grab (area, on_output_event, data);
g_object_weak_ref (data, grab_weak_ref_notify, area);
info = g_new0 (GrabInfo, 1);
info->grab_x = event->x;
info->grab_y = event->y;
info->output_x = output_x;
info->output_y = output_y;
g_object_set_data (G_OBJECT (output), "grab-info", info);
}
foo_scroll_area_invalidate (area);
}
else
{
if (foo_scroll_area_is_grabbed (area))
{
GrabInfo *info = g_object_get_data (G_OBJECT (output), "grab-info");
double scale = compute_scale (self, area);
int old_x, old_y;
int new_x, new_y;
int i;
GArray *edges, *snaps, *new_edges;
cc_display_monitor_get_geometry (output, &old_x, &old_y, NULL, NULL);
new_x = info->output_x + (event->x - info->grab_x) / scale;
new_y = info->output_y + (event->y - info->grab_y) / scale;
cc_display_monitor_set_position (output, new_x, new_y);
edges = g_array_new (TRUE, TRUE, sizeof (Edge));
snaps = g_array_new (TRUE, TRUE, sizeof (Snap));
new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
list_edges (self, edges);
list_snaps (output, edges, snaps);
g_array_sort (snaps, compare_snaps);
cc_display_monitor_set_position (output, old_x, old_y);
for (i = 0; i < snaps->len; ++i)
{
Snap *snap = &(g_array_index (snaps, Snap, i));
GArray *new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
cc_display_monitor_set_position (output, new_x + snap->dx, new_y + snap->dy);
g_array_set_size (new_edges, 0);
list_edges (self, new_edges);
if (config_is_aligned (self, new_edges))
{
g_array_free (new_edges, TRUE);
break;
}
else
{
cc_display_monitor_set_position (output, info->output_x, info->output_y);
}
}
g_array_free (new_edges, TRUE);
g_array_free (snaps, TRUE);
g_array_free (edges, TRUE);
if (event->type == FOO_BUTTON_RELEASE)
{
foo_scroll_area_end_grab (area, event);
g_free (g_object_get_data (G_OBJECT (output), "grab-info"));
g_object_set_data (G_OBJECT (output), "grab-info", NULL);
g_object_weak_unref (data, grab_weak_ref_notify, area);
update_apply_button (self);
#if 0
g_debug ("new position: %d %d %d %d", output->x, output->y, output->width, output->height);
#endif
}
foo_scroll_area_invalidate (area);
}
}
}
static void
on_canvas_event (FooScrollArea *area,
FooScrollAreaEvent *event,
gpointer data)
{
/* If the mouse exits the outputs, reset the cursor to the default. See
* on_output_event() for where we set the cursor to the movement cursor if
* it is over one of the outputs.
*/
set_cursor (GTK_WIDGET (area), GDK_BLANK_CURSOR);
}
static void
paint_background (FooScrollArea *area,
cairo_t *cr)
{
GdkRectangle viewport;
foo_scroll_area_get_viewport (area, &viewport);
cairo_set_source_rgba (cr, 0, 0, 0, 0.4);
cairo_rectangle (cr,
viewport.x, viewport.y,
viewport.width, viewport.height);
foo_scroll_area_add_input_from_fill (area, cr, on_canvas_event, NULL);
cairo_fill (cr);
}
static void
on_area_paint (FooScrollArea *area,
cairo_t *cr,
gpointer data)
{
CcDisplayPanel *self = data;
GList *connected_outputs = NULL;
GList *list;
int total_w, total_h;
paint_background (area, cr);
if (!self->priv->current_config)
return;
get_total_size (self, &total_w, &total_h);
connected_outputs = cc_display_config_get_monitors (self->priv->current_config);
for (list = connected_outputs; list != NULL; list = list->next)
{
int w, h;
double scale = compute_scale (self, area);
gint x, y;
int output_x, output_y;
CcDisplayMonitor *output = list->data;
GdkRectangle viewport;
cairo_save (cr);
foo_scroll_area_get_viewport (area, &viewport);
get_geometry (output, &output_x, &output_y, &w, &h);
if (cc_display_config_is_layout_logical (self->priv->current_config))
{
double scale = cc_display_monitor_get_scale (output);
w /= scale;
h /= scale;
}
viewport.height -= 2 * MARGIN;
viewport.width -= 2 * MARGIN;
x = output_x * scale + MARGIN + (viewport.width - total_w * scale) / 2.0;
y = output_y * scale + MARGIN + (viewport.height - total_h * scale) / 2.0;
cairo_set_source_rgba (cr, 0, 0, 0, 0);
cairo_rectangle (cr, x, y, w * scale + 0.5, h * scale + 0.5);
foo_scroll_area_add_input_from_fill (area, cr, on_output_event, output);
cairo_fill (cr);
cairo_translate (cr, x, y);
paint_output (self, cr, self->priv->current_config, output,
cc_display_panel_get_output_id (output),
w * scale, h * scale);
cairo_restore (cr);
if (cc_display_config_is_cloning (self->priv->current_config))
break;
}
}
static void
apply_current_configuration (CcDisplayPanel *self)
{
GError *error = NULL;
cc_display_config_apply (self->priv->current_config, &error);
/* re-read the configuration */
on_screen_changed (self);
if (error)
{
g_warning ("Error applying configuration: %s", error->message);
g_clear_error (&error);
}
}
static void
dialog_toplevel_focus_changed (GtkWindow *window,
GParamSpec *pspec,
CcDisplayPanel *self)
{
ensure_monitor_labels (self);
}
static void
show_arrange_displays_dialog (GtkButton *button,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkWidget *content_area, *area, *vbox, *label;
gint response;
/* Title of displays dialog when multiple monitors are present. */
priv->dialog = gtk_dialog_new_with_buttons (_("Arrange Combined Displays"),
GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))),
GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Apply"), GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (priv->dialog, "notify::has-toplevel-focus",
G_CALLBACK (dialog_toplevel_focus_changed), panel);
gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT);
gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT, FALSE);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (priv->dialog));
area = (GtkWidget *) foo_scroll_area_new ();
g_object_set_data (G_OBJECT (area), "panel", panel);
foo_scroll_area_set_min_size (FOO_SCROLL_AREA (area), 520, 290);
gtk_widget_set_margin_end (area, 12);
gtk_widget_set_margin_start (area, 12);
gtk_widget_set_size_request (area, 520, 290);
g_signal_connect (area, "paint",
G_CALLBACK (on_area_paint), panel);
g_signal_connect (area, "viewport_changed",
G_CALLBACK (on_viewport_changed), panel);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (vbox), area);
label = gtk_label_new (_("Drag displays to rearrange them"));
gtk_widget_set_margin_top (label, 12);
gtk_widget_set_margin_bottom (label, 12);
gtk_container_add (GTK_CONTAINER (vbox), label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL);
gtk_widget_show_all (vbox);
gtk_container_add (GTK_CONTAINER (content_area), vbox);
response = gtk_dialog_run (GTK_DIALOG (priv->dialog));
if (response == GTK_RESPONSE_ACCEPT)
apply_current_configuration (panel);
else if (response != GTK_RESPONSE_NONE)
{
/* re-read the previous configuration */
on_screen_changed (panel);
}
gtk_widget_destroy (priv->dialog);
priv->dialog = NULL;
}
static const gchar *
make_aspect_string (gint width,
gint height)
{
int ratio;
const gchar *aspect = NULL;
/* We use a number of Unicode characters below:
* is U+2236 RATIO
* is U+2009 THIN SPACE,
* × is U+00D7 MULTIPLICATION SIGN
*/
if (width && height) {
if (width > height)
ratio = width * 10 / height;
else
ratio = height * 10 / width;
switch (ratio) {
case 13:
aspect = "43";
break;
case 16:
aspect = "1610";
break;
case 17:
aspect = "169";
break;
case 23:
aspect = "219";
break;
case 12:
aspect = "54";
break;
/* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */
case 15:
aspect = "32";
break;
case 18:
aspect = "95";
break;
case 10:
aspect = "11";
break;
}
}
return aspect;
}
static char *
make_resolution_string (CcDisplayMode *mode)
{
const char *interlaced = cc_display_mode_is_interlaced (mode) ? "i" : "";
const char *aspect;
int width, height;
cc_display_mode_get_resolution (mode, &width, &height);
aspect = make_aspect_string (width, height);
if (aspect != NULL)
return g_strdup_printf ("%d×%d%s (%s)", width, height, interlaced, aspect);
else
return g_strdup_printf ("%d×%d%s", width, height, interlaced);
}
static GtkWidget *
list_box_item (const gchar *title,
const gchar *subtitle)
{
GtkWidget *item, *label, *row;
row = gtk_list_box_row_new ();
item = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_set_border_width (GTK_CONTAINER (item), 12);
label = gtk_label_new (title);
gtk_container_add (GTK_CONTAINER (item), label);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
label = gtk_label_new (subtitle);
gtk_container_add (GTK_CONTAINER (item), label);
gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL);
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_container_add (GTK_CONTAINER (row), item);
return row;
}
static gboolean
is_atsc_duplicate_freq (CcDisplayMode *mode,
CcDisplayMode *next_mode)
{
double freq, next_freq;
gboolean ret;
if (next_mode == NULL)
return FALSE;
freq = cc_display_mode_get_freq_f (mode);
next_freq = cc_display_mode_get_freq_f (next_mode);
ret = fabs (freq - (next_freq / 1000.0 * 1001.0)) < 0.01;
if (ret)
g_debug ("Next frequency %f is the NTSC variant of %f",
next_freq, freq);
return ret;
}
static int
sort_frequencies (gconstpointer a, gconstpointer b)
{
CcDisplayMode *mode_a = (CcDisplayMode *) a;
CcDisplayMode *mode_b = (CcDisplayMode *) b;
/* Highest to lowest */
if (cc_display_mode_get_freq_f (mode_a) > cc_display_mode_get_freq_f (mode_b))
return -1;
if (cc_display_mode_get_freq_f (mode_a) < cc_display_mode_get_freq_f (mode_b))
return 1;
return 0;
}
static void
setup_frequency_combo_box (CcDisplayPanel *panel,
CcDisplayMode *resolution_mode)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayMode *current_mode;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *res;
GSList *l, *frequencies;
guint i;
gboolean prev_dup;
current_mode = cc_display_monitor_get_mode (priv->current_output);
model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->freq_combo));
gtk_list_store_clear (GTK_LIST_STORE (model));
i = 0;
res = make_resolution_string (resolution_mode);
frequencies = g_slist_copy (g_hash_table_lookup (priv->res_freqs, res));
g_free (res);
frequencies = g_slist_sort (frequencies, sort_frequencies);
prev_dup = FALSE;
/* Look for 59.94Hz, and if it exists, remove the 60Hz option
* in favour of this NTSC/ATSC frequency.
* 60Hz is a "PC" frequency, 59.94Hz is a holdover
* from NTSC:
* https://en.wikipedia.org/wiki/NTSC#Lines_and_refresh_rate
*
* We also want to handle this for ~30Hz and ~120Hz */
for (l = frequencies; l != NULL; l = l->next)
{
CcDisplayMode *mode = l->data;
CcDisplayMode *next_mode;
gchar *freq;
gboolean dup;
if (l->next != NULL)
next_mode = l->next->data;
else
next_mode = NULL;
dup = is_atsc_duplicate_freq (mode, next_mode);
if (dup && mode != current_mode)
{
prev_dup = TRUE;
continue;
}
if (prev_dup)
{
/* translators: example string is "60 Hz (NTSC)"
* NTSC is https://en.wikipedia.org/wiki/NTSC */
freq = g_strdup_printf (_("%d Hz (NTSC)"),
(int) (roundf (cc_display_mode_get_freq_f (mode))));
}
else
{
/* translators: example string is "60 Hz" */
freq = g_strdup_printf (_("%d Hz"), cc_display_mode_get_freq (mode));
}
gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter,
-1, 0, freq, 1, mode, -1);
g_free (freq);
if (mode == current_mode)
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->freq_combo), &iter);
prev_dup = dup;
i++;
}
g_slist_free (frequencies);
if (i < 2)
{
gtk_widget_hide (priv->freq_combo);
return;
}
gtk_widget_show (priv->freq_combo);
if (gtk_combo_box_get_active (GTK_COMBO_BOX (priv->freq_combo)) == -1)
gtk_combo_box_set_active (GTK_COMBO_BOX (priv->freq_combo), 0);
}
static guint
n_supported_scales (CcDisplayMode *mode)
{
const double *scales = cc_display_mode_get_supported_scales (mode);
guint n = 0;
while (scales[n] != 0.0)
n++;
return n;
}
static void
setup_scaling_slider (CcDisplayPanel *panel,
CcDisplayMode *resolution_mode)
{
CcDisplayPanelPrivate *priv = panel->priv;
guint i;
guint n;
const double *scales;
scales = cc_display_mode_get_supported_scales (resolution_mode);
n = n_supported_scales (resolution_mode);
if (n > 0)
{
GtkAdjustment *adj;
double current_scale;
int current_index = -1;
current_scale = cc_display_monitor_get_scale (priv->current_output);
adj = gtk_range_get_adjustment (GTK_RANGE (priv->scale_slider));
gtk_scale_clear_marks (GTK_SCALE (priv->scale_slider));
gtk_adjustment_set_step_increment (adj, 1);
gtk_adjustment_set_lower (adj, 0);
gtk_adjustment_set_upper (adj, n-1);
gtk_scale_set_digits (GTK_SCALE (priv->scale_slider), 0);
for (i = 0; i < n; ++i)
{
double rounded_scale = round (scales[i] * 10) / 10;
double integral;
double fractional = modf (rounded_scale, &integral);
if (scales[i] == current_scale || (current_index < 0 && scales[i] == 1.0))
current_index = i;
if (fractional != 0 && (fractional != 0.5 || integral > 0))
continue;
gchar *s = g_strdup_printf ("%.2lg×", scales[i]);
gtk_scale_add_mark (GTK_SCALE (priv->scale_slider), i, GTK_POS_TOP, s);
g_free (s);
}
gtk_widget_show (priv->scale_slider);
gtk_range_set_value (GTK_RANGE (priv->scale_slider), current_index);
}
else
{
gtk_widget_hide (priv->scale_slider);
}
}
static void
free_mode_list (gpointer key,
gpointer value,
gpointer data)
{
g_slist_free (value);
}
static void
clear_res_freqs (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
if (priv->res_freqs)
{
g_hash_table_foreach (priv->res_freqs, free_mode_list, NULL);
g_hash_table_destroy (priv->res_freqs);
priv->res_freqs = NULL;
}
}
static void
setup_resolution_combo_box (CcDisplayPanel *panel,
GList *modes,
CcDisplayMode *current_mode)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkTreeModel *res_model;
GList *m;
clear_res_freqs (panel);
priv->res_freqs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
res_model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->res_combo));
gtk_list_store_clear (GTK_LIST_STORE (res_model));
for (m = modes; m != NULL; m = m->next)
{
CcDisplayMode *mode = m->data;
GSList *l;
gchar *res;
gint output_width, output_height, mode_width, mode_height;
if (!current_mode)
current_mode = mode;
cc_display_mode_get_resolution (mode, &mode_width, &mode_height);
cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (priv->current_output),
&output_width, &output_height);
if (!should_show_resolution (output_width, output_height, mode_width,
mode_height))
continue;
res = make_resolution_string (mode);
if ((l = g_hash_table_lookup (priv->res_freqs, res)) == NULL)
{
GtkTreeIter iter;
gint current_mode_width, current_mode_height;
gtk_list_store_insert_with_values (GTK_LIST_STORE (res_model), &iter,
-1, 0, res, 1, mode, -1);
cc_display_mode_get_resolution (current_mode,
&current_mode_width, &current_mode_height);
/* select the current mode in the combo box */
if (mode_width == current_mode_width && mode_height == current_mode_height
&& cc_display_mode_is_interlaced (mode) == cc_display_mode_is_interlaced (current_mode))
{
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->res_combo),
&iter);
}
}
l = g_slist_append (l, mode);
g_hash_table_replace (priv->res_freqs, res, l);
}
/* ensure a resolution is selected by default */
if (gtk_combo_box_get_active (GTK_COMBO_BOX (priv->res_combo)) == -1)
gtk_combo_box_set_active (GTK_COMBO_BOX (priv->res_combo), 0);
setup_frequency_combo_box (panel, current_mode);
setup_scaling_slider (panel, current_mode);
}
static void
setup_listbox_row_activated (GtkListBox *list_box,
GtkListBoxRow *row,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GList *modes;
gint index;
if (!row)
return;
index = gtk_list_box_row_get_index (row);
gtk_widget_set_sensitive (priv->config_grid, index != DISPLAY_MODE_OFF);
cc_display_monitor_set_active (priv->current_output,
(index != DISPLAY_MODE_OFF));
if (index == DISPLAY_MODE_MIRROR)
{
modes = cc_display_config_get_cloning_modes (priv->current_config);
cc_display_config_set_cloning (priv->current_config, TRUE);
}
else
{
cc_display_monitor_set_primary (priv->current_output,
(index == DISPLAY_MODE_PRIMARY));
cc_display_config_set_cloning (priv->current_config, FALSE);
modes = cc_display_monitor_get_modes (priv->current_output);
}
setup_resolution_combo_box (panel, modes,
cc_display_monitor_get_mode (priv->current_output));
update_apply_button (panel);
}
static void
rotate_left_clicked (GtkButton *button,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayRotation rotation;
gboolean active;
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
if (active)
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_right_button), FALSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->upside_down_button), FALSE);
rotation = CC_DISPLAY_ROTATION_90;
}
else
{
rotation = CC_DISPLAY_ROTATION_NONE;
}
cc_display_monitor_set_rotation (priv->current_output, rotation);
update_apply_button (panel);
}
static void
upside_down_clicked (GtkButton *button,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayRotation rotation;
gboolean active;
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
if (active)
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_left_button), FALSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_right_button), FALSE);
rotation = CC_DISPLAY_ROTATION_180;
}
else
{
rotation = CC_DISPLAY_ROTATION_NONE;
}
cc_display_monitor_set_rotation (priv->current_output, rotation);
update_apply_button (panel);
}
static void
rotate_right_clicked (GtkButton *button,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayRotation rotation;
gboolean active;
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
if (active)
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_left_button), FALSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->upside_down_button), FALSE);
rotation = CC_DISPLAY_ROTATION_270;
}
else
{
rotation = CC_DISPLAY_ROTATION_NONE;
}
cc_display_monitor_set_rotation (priv->current_output, rotation);
update_apply_button (panel);
}
static const double known_diagonals[] = {
12.1,
13.3,
15.6
};
static char *
diagonal_to_str (double d)
{
int i;
for (i = 0; i < G_N_ELEMENTS (known_diagonals); i++)
{
double delta;
delta = fabs(known_diagonals[i] - d);
if (delta < 0.1)
return g_strdup_printf ("%0.1lf\"", known_diagonals[i]);
}
return g_strdup_printf ("%d\"", (int) (d + 0.5));
}
static char *
make_display_size_string (int width_mm,
int height_mm)
{
char *inches = NULL;
if (width_mm > 0 && height_mm > 0)
{
double d = sqrt (width_mm * width_mm + height_mm * height_mm);
inches = diagonal_to_str (d / 25.4);
}
return inches;
}
static void
freq_combo_changed (GtkComboBox *combo,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkTreeModel *model;
GtkTreeIter iter;
CcDisplayMode *mode;
model = gtk_combo_box_get_model (combo);
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
{
gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, 1, &mode, -1);
if (mode)
{
cc_display_monitor_set_mode (priv->current_output, mode);
update_apply_button (panel);
}
}
}
static void
res_combo_changed (GtkComboBox *combo,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkTreeModel *res_model;
GtkTreeIter iter;
CcDisplayMode *mode;
res_model = gtk_combo_box_get_model (combo);
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
{
gtk_tree_model_get (GTK_TREE_MODEL (res_model), &iter, 1, &mode, -1);
if (cc_display_config_is_cloning (priv->current_config))
{
GList *outputs, *l;
outputs = cc_display_config_get_monitors (priv->current_config);
for (l = outputs; l != NULL; l = l->next)
cc_display_monitor_set_mode (CC_DISPLAY_MONITOR (l->data), mode);
}
else
{
cc_display_monitor_set_mode (priv->current_output, mode);
}
update_apply_button (panel);
setup_frequency_combo_box (panel, mode);
setup_scaling_slider (panel, mode);
}
}
static void
sanity_check_rotation (CcDisplayMonitor *output)
{
CcDisplayRotation rotation;
rotation = cc_display_monitor_get_rotation (output);
/* other options such as reflection are not supported */
rotation &= (CC_DISPLAY_ROTATION_NONE | CC_DISPLAY_ROTATION_90
| CC_DISPLAY_ROTATION_180 | CC_DISPLAY_ROTATION_270);
if (rotation == 0)
rotation = CC_DISPLAY_ROTATION_NONE;
cc_display_monitor_set_rotation (output, rotation);
}
static gboolean
should_show_rotation (CcDisplayPanel *panel,
CcDisplayMonitor *output)
{
CcDisplayPanelPrivate *priv = panel->priv;
gboolean supports_rotation;
supports_rotation = cc_display_monitor_supports_rotation (output,
CC_DISPLAY_ROTATION_90 |
CC_DISPLAY_ROTATION_180 |
CC_DISPLAY_ROTATION_270);
/* Doesn't support rotation at all */
if (!supports_rotation)
return FALSE;
/* We can always rotate displays that aren't builtin */
if (!cc_display_monitor_is_builtin (output))
return TRUE;
/* Only offer rotation if there's no accelerometer */
return !priv->has_accelerometer;
}
static void
underscan_switch_toggled (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
gboolean value;
value = gtk_switch_get_active (GTK_SWITCH (priv->scaling_switch));
cc_display_monitor_set_underscanning (priv->current_output, value);
update_apply_button (panel);
}
static double
scale_slider_get_selected_scale (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayMode *mode = cc_display_monitor_get_mode (priv->current_output);
const double *scales = cc_display_mode_get_supported_scales (mode);
int selected = gtk_range_get_value (GTK_RANGE (priv->scale_slider));
g_return_val_if_fail (selected < n_supported_scales (mode), 1.0);
return scales[selected];
}
static void
scale_slider_changed (GtkRange *slider,
CcDisplayPanel *panel)
{
cc_display_monitor_set_scale (panel->priv->current_output,
scale_slider_get_selected_scale (panel));
update_apply_button (panel);
}
static char *
scale_slider_format_value (GtkScale *slider,
double value,
CcDisplayPanel *panel)
{
return g_strdup_printf ("%.3g", scale_slider_get_selected_scale (panel));
}
static void
show_setup_dialog (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkWidget *listbox = NULL, *content_area, *item, *box, *frame, *preview;
GtkWidget *label, *rotate_box;
gint width_mm, height_mm, old_width, old_height, grid_pos;
gchar *str;
gboolean clone, was_clone, primary, active;
GtkListStore *res_model, *freq_model;
GtkCellRenderer *renderer;
CcDisplayRotation rotation;
gboolean show_rotation;
gint response, num_active_outputs = 0;
GList *outputs, *l;
outputs = cc_display_config_get_monitors (priv->current_config);
/* count the number of active */
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
if (cc_display_monitor_is_active (output))
num_active_outputs++;
}
priv->dialog = gtk_dialog_new_with_buttons (cc_display_monitor_get_display_name (priv->current_output),
GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))),
GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Apply"), GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (priv->dialog, "notify::has-toplevel-focus",
G_CALLBACK (dialog_toplevel_focus_changed), panel);
gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT);
gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT, FALSE);
gtk_window_set_resizable (GTK_WINDOW (priv->dialog), FALSE);
content_area = gtk_dialog_get_content_area (GTK_DIALOG (priv->dialog));
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_start (box, 12);
gtk_widget_set_margin_end (box, 12);
gtk_widget_set_margin_top (box, 6);
gtk_widget_set_margin_bottom (box, 12);
gtk_container_add (GTK_CONTAINER (content_area), box);
/* configuration grid */
grid_pos = 0;
priv->config_grid = gtk_grid_new ();
gtk_widget_set_margin_start (priv->config_grid, 36);
gtk_widget_set_margin_end (priv->config_grid, 36);
gtk_widget_set_margin_bottom (priv->config_grid, 6);
gtk_grid_set_column_spacing (GTK_GRID (priv->config_grid), 12);
gtk_grid_set_row_spacing (GTK_GRID (priv->config_grid), 12);
/* preview */
preview = display_preview_new (panel, priv->current_output,
priv->current_config,
cc_display_panel_get_output_id (priv->current_output),
DISPLAY_PREVIEW_SETUP_HEIGHT);
gtk_grid_attach (GTK_GRID (priv->config_grid), preview, 0, grid_pos, 2, 1);
grid_pos++;
/* rotation */
show_rotation = should_show_rotation (panel, priv->current_output);
rotation = cc_display_monitor_get_rotation (priv->current_output);
if (show_rotation)
{
rotate_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_margin_bottom (rotate_box, 12);
gtk_style_context_add_class (gtk_widget_get_style_context (rotate_box),
GTK_STYLE_CLASS_LINKED);
gtk_grid_attach (GTK_GRID (priv->config_grid), rotate_box, 0, grid_pos, 2, 1);
gtk_widget_set_halign (rotate_box, GTK_ALIGN_CENTER);
grid_pos++;
if (cc_display_monitor_supports_rotation (priv->current_output,
CC_DISPLAY_ROTATION_90))
{
priv->rotate_left_button = gtk_toggle_button_new ();
gtk_widget_set_tooltip_text (priv->rotate_left_button, _("Rotate counterclockwise by 90\xc2\xb0"));
if (rotation == CC_DISPLAY_ROTATION_90)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_left_button), TRUE);
g_signal_connect (priv->rotate_left_button, "clicked",
G_CALLBACK (rotate_left_clicked), panel);
g_signal_connect_swapped (priv->rotate_left_button, "clicked",
G_CALLBACK (gtk_widget_queue_draw), preview);
gtk_container_add (GTK_CONTAINER (priv->rotate_left_button),
gtk_image_new_from_icon_name ("object-rotate-left-symbolic",
GTK_ICON_SIZE_BUTTON));
gtk_widget_set_halign (priv->rotate_left_button, GTK_ALIGN_END);
gtk_container_add (GTK_CONTAINER (rotate_box), priv->rotate_left_button);
}
if (cc_display_monitor_supports_rotation (priv->current_output,
CC_DISPLAY_ROTATION_180))
{
priv->upside_down_button = gtk_toggle_button_new ();
gtk_widget_set_tooltip_text (priv->upside_down_button, _("Rotate by 180\xc2\xb0"));
if (rotation == CC_DISPLAY_ROTATION_180)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->upside_down_button), TRUE);
g_signal_connect (priv->upside_down_button, "clicked",
G_CALLBACK (upside_down_clicked), panel);
g_signal_connect_swapped (priv->upside_down_button, "clicked",
G_CALLBACK (gtk_widget_queue_draw), preview);
gtk_container_add (GTK_CONTAINER (priv->upside_down_button),
gtk_image_new_from_icon_name ("object-flip-vertical-symbolic",
GTK_ICON_SIZE_BUTTON));
gtk_widget_set_halign (priv->upside_down_button, GTK_ALIGN_FILL);
gtk_container_add (GTK_CONTAINER (rotate_box), priv->upside_down_button);
}
if (cc_display_monitor_supports_rotation (priv->current_output,
CC_DISPLAY_ROTATION_270))
{
priv->rotate_right_button = gtk_toggle_button_new ();
gtk_widget_set_tooltip_text (priv->rotate_right_button, _("Rotate clockwise by 90\xc2\xb0"));
if (rotation == CC_DISPLAY_ROTATION_270)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rotate_right_button), TRUE);
g_signal_connect (priv->rotate_right_button, "clicked",
G_CALLBACK (rotate_right_clicked), panel);
g_signal_connect_swapped (priv->rotate_right_button, "clicked",
G_CALLBACK (gtk_widget_queue_draw), preview);
gtk_container_add (GTK_CONTAINER (priv->rotate_right_button),
gtk_image_new_from_icon_name ("object-rotate-right-symbolic",
GTK_ICON_SIZE_BUTTON));
gtk_widget_set_halign (priv->rotate_right_button, GTK_ALIGN_START);
gtk_container_add (GTK_CONTAINER (rotate_box), priv->rotate_right_button);
}
}
/* size */
cc_display_monitor_get_physical_size (priv->current_output, &width_mm, &height_mm);
str = make_display_size_string (width_mm, height_mm);
if (str != NULL)
{
label = gtk_label_new (_("Size"));
gtk_style_context_add_class (gtk_widget_get_style_context (label),
GTK_STYLE_CLASS_DIM_LABEL);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 0, grid_pos, 1, 1);
gtk_widget_set_halign (label, GTK_ALIGN_END);
label = gtk_label_new (str);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 1, grid_pos, 1, 1);
gtk_widget_set_halign (label, GTK_ALIGN_START);
g_free (str);
grid_pos++;
}
/* aspect ratio */
label = gtk_label_new (_("Aspect Ratio"));
gtk_style_context_add_class (gtk_widget_get_style_context (label),
GTK_STYLE_CLASS_DIM_LABEL);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 0, grid_pos, 1, 1);
gtk_widget_set_halign (label, GTK_ALIGN_END);
{
int w, h;
cc_display_mode_get_resolution (cc_display_monitor_get_preferred_mode (priv->current_output),
&w, &h);
label = gtk_label_new (make_aspect_string (w, h));
}
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 1, grid_pos, 1, 1);
gtk_widget_set_halign (label, GTK_ALIGN_START);
grid_pos++;
/* resolution combo box */
res_model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
priv->res_combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (res_model));
g_object_unref (res_model);
renderer = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->res_combo), renderer, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->res_combo), renderer, "text", 0);
g_signal_connect (priv->res_combo, "changed", G_CALLBACK (res_combo_changed),
panel);
g_signal_connect_swapped (priv->res_combo, "changed",
G_CALLBACK (gtk_widget_queue_draw), preview);
label = gtk_label_new (_("Resolution"));
gtk_style_context_add_class (gtk_widget_get_style_context (label),
GTK_STYLE_CLASS_DIM_LABEL);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 0, grid_pos, 1, 1);
gtk_grid_attach (GTK_GRID (priv->config_grid), priv->res_combo, 1, grid_pos, 1, 1);
grid_pos++;
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_halign (priv->res_combo, GTK_ALIGN_START);
/* overscan */
if (!cc_display_monitor_is_builtin (priv->current_output) &&
cc_display_monitor_supports_underscanning (priv->current_output))
{
priv->scaling_switch = gtk_switch_new ();
gtk_switch_set_active (GTK_SWITCH (priv->scaling_switch),
cc_display_monitor_get_underscanning (priv->current_output));
g_signal_connect_swapped (G_OBJECT (priv->scaling_switch), "notify::active",
G_CALLBACK (underscan_switch_toggled), panel);
label = gtk_label_new (_("Adjust for TV"));
gtk_style_context_add_class (gtk_widget_get_style_context (label),
GTK_STYLE_CLASS_DIM_LABEL);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 0, grid_pos, 1, 1);
gtk_grid_attach (GTK_GRID (priv->config_grid), priv->scaling_switch, 1, grid_pos, 1, 1);
grid_pos++;
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_halign (priv->scaling_switch, GTK_ALIGN_START);
}
/* frequency combo box */
freq_model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
priv->freq_combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (freq_model));
g_object_unref (freq_model);
renderer = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->freq_combo), renderer, TRUE);
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->freq_combo), renderer, "text", 0);
g_signal_connect (priv->freq_combo, "changed", G_CALLBACK (freq_combo_changed),
panel);
gtk_grid_attach (GTK_GRID (priv->config_grid), priv->freq_combo, 1, grid_pos, 1, 1);
gtk_widget_set_halign (priv->freq_combo, GTK_ALIGN_START);
gtk_widget_set_no_show_all (priv->freq_combo, TRUE);
label = gtk_label_new (_("Refresh Rate"));
gtk_style_context_add_class (gtk_widget_get_style_context (label),
GTK_STYLE_CLASS_DIM_LABEL);
gtk_grid_attach (GTK_GRID (priv->config_grid), label, 0, grid_pos, 1, 1);
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_no_show_all (label, TRUE);
g_object_bind_property (priv->freq_combo, "visible",
label, "visible", G_BINDING_BIDIRECTIONAL);
grid_pos++;
/* scale */
priv->scale_slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, NULL);
gtk_widget_set_no_show_all (priv->scale_slider, TRUE);
g_signal_connect (priv->scale_slider, "format-value",
G_CALLBACK (scale_slider_format_value), panel);
g_signal_connect (priv->scale_slider, "value-changed",
G_CALLBACK (scale_slider_changed), panel);
gtk_scale_set_draw_value (GTK_SCALE (priv->scale_slider), TRUE);
gtk_range_set_round_digits (GTK_RANGE (priv->scale_slider), 2);
gtk_scale_set_value_pos (GTK_SCALE (priv->scale_slider), GTK_POS_BOTTOM);
gtk_scale_set_has_origin (GTK_SCALE (priv->scale_slider), FALSE);
gtk_grid_attach (GTK_GRID (priv->config_grid),
priv->scale_slider, 1, grid_pos, 1, 1);
gtk_widget_set_halign (priv->freq_combo, GTK_ALIGN_CENTER);
grid_pos++;
was_clone = clone = cc_display_config_is_cloning (priv->current_config);
primary = cc_display_monitor_is_primary (priv->current_output);
active = cc_display_monitor_is_active (priv->current_output);
if (num_active_outputs > 1 || !active)
{
frame = gtk_frame_new (NULL);
gtk_container_add (GTK_CONTAINER (box), frame);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
listbox = gtk_list_box_new ();
gtk_container_add (GTK_CONTAINER (frame), listbox);
gtk_list_box_set_header_func (GTK_LIST_BOX (listbox),
cc_list_box_update_header_func,
NULL, NULL);
g_signal_connect (listbox, "row-selected",
G_CALLBACK (setup_listbox_row_activated), panel);
g_signal_connect_swapped (listbox, "row-selected",
G_CALLBACK (gtk_widget_queue_draw), preview);
gtk_widget_show (listbox);
item = list_box_item (_("Primary"),
_("Show the top bar and Activities Overview on this display"));
gtk_container_add (GTK_CONTAINER (listbox), item);
if (primary)
gtk_list_box_select_row (GTK_LIST_BOX (listbox),
GTK_LIST_BOX_ROW (item));
item = list_box_item (_("Secondary Display"),
_("Join this display with another to create an extra workspace"));
gtk_container_add (GTK_CONTAINER (listbox), item);
if (!primary && !clone)
gtk_list_box_select_row (GTK_LIST_BOX (listbox),
GTK_LIST_BOX_ROW (item));
#if 0
item = list_box_item (_("Presentation"),
_("Show slideshows and media only"));
gtk_container_add (GTK_CONTAINER (listbox), item);
#endif
/* translators: "Mirror" describes when both displays show the same view */
item = list_box_item (_("Mirror"),
_("Show your existing view on both displays"));
gtk_container_add (GTK_CONTAINER (listbox), item);
if (clone && active)
gtk_list_box_select_row (GTK_LIST_BOX (listbox),
GTK_LIST_BOX_ROW (item));
item = list_box_item (_("Turn Off"),
_("Dont use this display"));
gtk_container_add (GTK_CONTAINER (listbox), item);
if (!active)
gtk_list_box_select_row (GTK_LIST_BOX (listbox),
GTK_LIST_BOX_ROW (item));
}
else
{
setup_resolution_combo_box (panel,
cc_display_monitor_get_modes (priv->current_output),
cc_display_monitor_get_mode (priv->current_output));
}
content_area = gtk_dialog_get_content_area (GTK_DIALOG (priv->dialog));
gtk_container_add (GTK_CONTAINER (box), priv->config_grid);
gtk_widget_show_all (box);
cc_display_monitor_get_geometry (priv->current_output, NULL, NULL,
&old_width, &old_height);
response = gtk_dialog_run (GTK_DIALOG (priv->dialog));
if (response == GTK_RESPONSE_ACCEPT)
{
GtkListBoxRow *row;
gboolean active = TRUE;
if (g_hash_table_size (output_ids) > 1)
{
gboolean primary_chosen = FALSE;
row = gtk_list_box_get_selected_row (GTK_LIST_BOX (listbox));
switch (gtk_list_box_row_get_index (row))
{
case DISPLAY_MODE_PRIMARY:
primary = TRUE;
clone = FALSE;
break;
#if 0
case DISPLAY_MODE_PRESENTATION:
gnome_rr_config_set_clone (priv->current_configuration, FALSE);
primary = FALSE;
clone = FALSE;
break;
#endif
case DISPLAY_MODE_MIRROR:
clone = TRUE;
break;
case DISPLAY_MODE_SECONDARY:
primary = FALSE;
clone = FALSE;
break;
case DISPLAY_MODE_OFF:
primary = FALSE;
clone = FALSE;
active = FALSE;
break;
}
cc_display_monitor_set_active (priv->current_output, active);
cc_display_monitor_set_primary (priv->current_output, primary);
cc_display_config_set_cloning (priv->current_config, clone);
for (l = outputs; l != NULL; l = l->next)
{
CcDisplayMonitor *output = l->data;
if (!cc_display_monitor_is_active (output))
continue;
if (clone)
{
/* set all active outputs to the same size and position when
* cloning */
cc_display_monitor_set_mode (output,
cc_display_monitor_get_mode (priv->current_output));
cc_display_monitor_set_position (output, 0, 0);
}
else if (output != priv->current_output)
{
/* ensure no other outputs are primary if this output is now
* primary, or find another output to set as primary if this
* output is no longer primary */
if (primary)
{
cc_display_monitor_set_primary (output, FALSE);
}
else if (!primary_chosen)
{
cc_display_monitor_set_primary (output, TRUE);
primary_chosen = TRUE;
}
}
}
sanity_check_rotation (priv->current_output);
/* The new API checks with mutter every time
update_apply_button() is called and mutter is very
strict about this so it already does these adjustments
internally. */
if (!priv->have_new_dbus_api)
{
/* if the display was previously in clone mode, ensure the outputs
* are arranged correctly */
if ((was_clone && !clone))
lay_out_outputs_horizontally (panel);
if (!clone)
realign_outputs_after_resolution_change (panel,
priv->current_output,
old_width, old_height, rotation);
}
}
else
{
/* check rotation */
sanity_check_rotation (priv->current_output);
}
apply_current_configuration (panel);
}
else if (response != GTK_RESPONSE_NONE)
{
/* changes cancelled, so re-read the current configuration */
on_screen_changed (panel);
}
priv->rotate_left_button = NULL;
priv->rotate_right_button = NULL;
priv->res_combo = NULL;
priv->freq_combo = NULL;
clear_res_freqs (panel);
gtk_widget_destroy (priv->dialog);
priv->dialog = NULL;
}
static void
cc_display_panel_night_light_activated (GtkListBox *listbox,
GtkWidget *row,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
GtkWindow *toplevel;
toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
cc_night_light_dialog_present (priv->night_light_dialog, toplevel);
}
static void
cc_display_panel_box_row_activated (GtkListBox *listbox,
GtkWidget *row,
CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcDisplayMonitor *output;
gtk_list_box_select_row (listbox, NULL);
output = g_object_get_data (G_OBJECT (row), "cc-display-monitor");
if (!output)
return;
priv->current_output = output;
show_setup_dialog (panel);
}
static void
mapped_cb (CcDisplayPanel *panel)
{
CcDisplayPanelPrivate *priv = panel->priv;
CcShell *shell;
GtkWidget *toplevel;
shell = cc_panel_get_shell (CC_PANEL (panel));
toplevel = cc_shell_get_toplevel (shell);
if (toplevel)
priv->focus_id = g_signal_connect (toplevel, "notify::has-toplevel-focus",
G_CALLBACK (dialog_toplevel_focus_changed), panel);
}
static void
cc_display_panel_up_client_changed (UpClient *client,
GParamSpec *pspec,
CcDisplayPanel *self)
{
CcDisplayPanelPrivate *priv = self->priv;
gboolean lid_is_closed;
lid_is_closed = up_client_get_lid_is_closed (client);
if (lid_is_closed != priv->lid_is_closed)
{
priv->lid_is_closed = lid_is_closed;
on_screen_changed (self);
}
}
static void
shell_proxy_ready (GObject *source,
GAsyncResult *res,
CcDisplayPanel *self)
{
GDBusProxy *proxy;
GError *error = NULL;
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (!proxy)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to contact gnome-shell: %s", error->message);
g_error_free (error);
return;
}
self->priv->shell_proxy = proxy;
ensure_monitor_labels (self);
}
static void
update_has_accel (CcDisplayPanel *self)
{
GVariant *v;
if (self->priv->iio_sensor_proxy == NULL)
{
g_debug ("Has no accelerometer");
self->priv->has_accelerometer = FALSE;
return;
}
v = g_dbus_proxy_get_cached_property (self->priv->iio_sensor_proxy, "HasAccelerometer");
if (v)
{
self->priv->has_accelerometer = g_variant_get_boolean (v);
g_variant_unref (v);
}
else
{
self->priv->has_accelerometer = FALSE;
}
g_debug ("Has %saccelerometer", self->priv->has_accelerometer ? "" : "no ");
}
static void
sensor_proxy_properties_changed_cb (GDBusProxy *proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
CcDisplayPanel *self)
{
GVariantDict dict;
g_variant_dict_init (&dict, changed_properties);
if (g_variant_dict_contains (&dict, "HasAccelerometer"))
update_has_accel (self);
}
static void
sensor_proxy_appeared_cb (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
CcDisplayPanel *self = user_data;
g_debug ("SensorProxy appeared");
self->priv->iio_sensor_proxy = g_dbus_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"net.hadess.SensorProxy",
"/net/hadess/SensorProxy",
"net.hadess.SensorProxy",
NULL,
NULL);
g_return_if_fail (self->priv->iio_sensor_proxy);
g_signal_connect (self->priv->iio_sensor_proxy, "g-properties-changed",
G_CALLBACK (sensor_proxy_properties_changed_cb), self);
update_has_accel (self);
}
static void
sensor_proxy_vanished_cb (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
CcDisplayPanel *self = user_data;
g_debug ("SensorProxy vanished");
g_clear_object (&self->priv->iio_sensor_proxy);
update_has_accel (self);
}
static void
night_light_sync_label (GtkWidget *label, GSettings *settings)
{
gboolean ret = g_settings_get_boolean (settings, "night-light-enabled");
gtk_label_set_label (GTK_LABEL (label),
/* TRANSLATORS: the state of the night light setting */
ret ? _("On") : _("Off"));
}
static void
settings_color_changed_cb (GSettings *settings, gchar *key, GtkWidget *label)
{
if (g_strcmp0 (key, "night-light-enabled") == 0)
night_light_sync_label (label, settings);
}
static GtkWidget *
make_night_light_widget (CcDisplayPanel *self)
{
CcDisplayPanelPrivate *priv = DISPLAY_PANEL_PRIVATE (self);
GtkWidget *frame, *row, *box, *label;
GtkWidget *night_light_listbox;
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
night_light_listbox = gtk_list_box_new ();
gtk_list_box_set_selection_mode (GTK_LIST_BOX (night_light_listbox),
GTK_SELECTION_NONE);
gtk_container_add (GTK_CONTAINER (frame), night_light_listbox);
g_signal_connect (night_light_listbox, "row-activated",
G_CALLBACK (cc_display_panel_night_light_activated),
self);
row = gtk_list_box_row_new ();
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50);
gtk_container_add (GTK_CONTAINER (row), box);
gtk_container_add (GTK_CONTAINER (night_light_listbox), row);
label = gtk_label_new (_("_Night Light"));
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
gtk_widget_set_margin_start (label, 20);
gtk_widget_set_margin_end (label, 20);
gtk_widget_set_margin_top (label, 12);
gtk_widget_set_margin_bottom (label, 12);
gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
label = gtk_label_new ("");
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_widget_set_margin_start (label, 24);
gtk_widget_set_margin_end (label, 24);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
g_signal_connect_object (priv->settings_color, "changed",
G_CALLBACK (settings_color_changed_cb), label, 0);
night_light_sync_label (label, priv->settings_color);
return frame;
}
static void
init_config_manager (GObject *source,
GAsyncResult *res,
CcDisplayPanel *self)
{
GVariant *variant;
GDBusConnection *bus = G_DBUS_CONNECTION (source);
GError *error = NULL;
variant = g_dbus_connection_call_finish (bus, res, &error);
if (!variant)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
goto out;
}
else
{
GVariant *value = NULL;
g_variant_get_child (variant, 0, "v", &value);
g_variant_get (value, "b", &self->priv->have_new_dbus_api);
g_variant_unref (value);
g_variant_unref (variant);
}
if (self->priv->have_new_dbus_api)
self->priv->manager = cc_display_config_manager_dbus_new ();
else
self->priv->manager = cc_display_config_manager_rr_new ();
g_signal_connect_object (self->priv->manager, "changed",
G_CALLBACK (on_screen_changed),
self,
G_CONNECT_SWAPPED);
out:
g_clear_error (&error);
g_object_unref (bus);
}
static void
session_bus_ready (GObject *source,
GAsyncResult *res,
CcDisplayPanel *self)
{
GDBusConnection *bus;
GError *error = NULL;
bus = g_bus_get_finish (res, &error);
if (!bus)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_warning ("Failed to get session bus: %s", error->message);
gtk_stack_set_visible_child_name (GTK_STACK (self->priv->stack), "error");
}
g_error_free (error);
return;
}
g_dbus_connection_call (bus,
"org.gnome.Mutter.DisplayConfig",
"/org/gnome/Mutter/DisplayConfig",
"org.freedesktop.DBus.Properties",
"Get",
g_variant_new ("(ss)",
"org.gnome.Mutter.DisplayConfig",
"IsExperimentalApiEnabled"),
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
self->priv->shell_cancellable,
(GAsyncReadyCallback) init_config_manager,
self);
}
static void
cc_display_panel_init (CcDisplayPanel *self)
{
CcDisplayPanelPrivate *priv;
GtkWidget *frame, *vbox;
GSettings *settings;
g_resources_register (cc_display_get_resource ());
priv = self->priv = DISPLAY_PANEL_PRIVATE (self);
priv->stack = gtk_stack_new ();
gtk_stack_add_named (GTK_STACK (priv->stack),
gtk_label_new (_("Could not get screen information")),
"error");
gtk_container_add (GTK_CONTAINER (self), priv->stack);
gtk_widget_show_all (priv->stack);
settings = g_settings_new ("org.gnome.desktop.background");
priv->background = gnome_bg_new ();
gnome_bg_load_from_preferences (priv->background, settings);
g_object_unref (settings);
priv->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
priv->night_light_dialog = cc_night_light_dialog_new ();
priv->settings_color = g_settings_new ("org.gnome.settings-daemon.plugins.color");
output_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 22);
gtk_stack_add_named (GTK_STACK (priv->stack), vbox, "main");
gtk_stack_set_visible_child (GTK_STACK (priv->stack), vbox);
frame = gtk_frame_new (NULL);
gtk_widget_set_margin_start (vbox, 134);
gtk_widget_set_margin_end (vbox, 134);
gtk_widget_set_margin_top (vbox, 22);
gtk_widget_set_margin_bottom (vbox, 22);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (vbox), frame);
priv->displays_listbox = gtk_list_box_new ();
gtk_list_box_set_header_func (GTK_LIST_BOX (priv->displays_listbox),
cc_list_box_update_header_func, NULL,
NULL);
g_signal_connect (priv->displays_listbox, "row-activated",
G_CALLBACK (cc_display_panel_box_row_activated),
self);
gtk_container_add (GTK_CONTAINER (frame), priv->displays_listbox);
priv->arrange_button = gtk_button_new_with_mnemonic (_("_Arrange Combined Displays"));
g_signal_connect (priv->arrange_button, "clicked",
G_CALLBACK (show_arrange_displays_dialog), self);
gtk_widget_set_halign (priv->arrange_button, GTK_ALIGN_CENTER);
gtk_container_add (GTK_CONTAINER (vbox), priv->arrange_button);
gtk_container_add (GTK_CONTAINER (vbox), make_night_light_widget (self));
gtk_widget_show_all (vbox);
self->priv->up_client = up_client_new ();
if (up_client_get_lid_is_present (self->priv->up_client))
{
/* Connect to the "changed" signal to track changes to "lid-is-closed"
* property. Connecting to "notify::lid-is-closed" would be preferable,
* but currently doesn't work as expected:
* https://bugs.freedesktop.org/show_bug.cgi?id=43001
*/
g_signal_connect (self->priv->up_client, "notify::lid-is-closed",
G_CALLBACK (cc_display_panel_up_client_changed), self);
cc_display_panel_up_client_changed (self->priv->up_client, NULL, self);
}
else
g_clear_object (&self->priv->up_client);
g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL);
self->priv->shell_cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL,
"org.gnome.Shell",
"/org/gnome/Shell",
"org.gnome.Shell",
self->priv->shell_cancellable,
(GAsyncReadyCallback) shell_proxy_ready,
self);
g_bus_get (G_BUS_TYPE_SESSION,
self->priv->shell_cancellable,
(GAsyncReadyCallback) session_bus_ready,
self);
priv->sensor_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
"net.hadess.SensorProxy",
G_BUS_NAME_WATCHER_FLAGS_NONE,
sensor_proxy_appeared_cb,
sensor_proxy_vanished_cb,
self,
NULL);
}