diff --git a/configure.ac b/configure.ac index 18a24b080..427cf721c 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,8 @@ MUTTER_PC_MODULES=" xcomposite >= 0.2 xfixes xrender xdamage xi >= 1.6.0 $CLUTTER_PACKAGE >= 1.14.3 cogl-1.0 >= 1.13.3 + upower-glib > 0.9.11 + gnome-desktop-3.0 " GLIB_GSETTINGS diff --git a/po/POTFILES.in b/po/POTFILES.in index 1e1b5a247..fb893350e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -12,6 +12,7 @@ src/core/display.c src/core/errors.c src/core/keybindings.c src/core/main.c +src/core/monitor.c src/core/mutter.c src/core/prefs.c src/core/screen.c @@ -23,12 +24,9 @@ src/core/xprops.c src/mutter.desktop.in src/mutter-wm.desktop.in src/org.gnome.mutter.gschema.xml.in -src/tools/mutter-message.c src/ui/frames.c src/ui/menu.c src/ui/metaaccellabel.c src/ui/resizepopup.c src/ui/theme.c src/ui/theme-parser.c -src/ui/theme-viewer.c - diff --git a/src/Makefile.am b/src/Makefile.am index 0dbd77887..7fca4f23d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,6 +37,7 @@ INCLUDES += \ mutter_built_sources = \ $(dbus_idle_built_sources) \ + $(dbus_xrandr_built_sources) \ mutter-enum-types.h \ mutter-enum-types.c \ wayland/xserver-protocol.c \ @@ -105,6 +106,8 @@ libmutter_wayland_la_SOURCES = \ ui/draw-workspace.h \ core/edge-resistance.c \ core/edge-resistance.h \ + core/edid-parse.c \ + core/edid.h \ core/errors.c \ meta/errors.h \ core/frame.c \ @@ -125,6 +128,11 @@ libmutter_wayland_la_SOURCES = \ core/meta-cursor-tracker-private.h \ core/meta-idle-monitor.c \ core/meta-idle-monitor-private.h \ + core/meta-xrandr-shared.h \ + core/monitor.c \ + core/monitor-config.c \ + core/monitor-private.h \ + core/monitor-xrandr.c \ core/mutter-Xatomtype.h \ core/place.c \ core/place.h \ @@ -353,6 +361,15 @@ mutter-enum-types.c: stamp-mutter-enum-types.h mutter-enum-types.c.in cp xgen-tetc mutter-enum-types.c && \ rm -f xgen-tetc +dbus_xrandr_built_sources = meta-dbus-xrandr.c meta-dbus-xrandr.h + +$(dbus_xrandr_built_sources) : Makefile.am xrandr.xml + $(AM_V_GEN)gdbus-codegen \ + --interface-prefix org.gnome.Mutter \ + --c-namespace MetaDBus \ + --generate-c-code meta-dbus-xrandr \ + $(srcdir)/xrandr.xml + dbus_idle_built_sources = meta-dbus-idle-monitor.c meta-dbus-idle-monitor.h $(dbus_idle_built_sources) : Makefile.am idle-monitor.xml diff --git a/src/compositor/meta-plugin-manager.c b/src/compositor/meta-plugin-manager.c index 2ae100e84..fa9fe96e3 100644 --- a/src/compositor/meta-plugin-manager.c +++ b/src/compositor/meta-plugin-manager.c @@ -85,12 +85,20 @@ meta_plugin_manager_load (const gchar *plugin_name) g_free (path); } +static void +on_confirm_display_change (MetaMonitorManager *monitors, + MetaPluginManager *plugin_mgr) +{ + meta_plugin_manager_confirm_display_change (plugin_mgr); +} + MetaPluginManager * meta_plugin_manager_new (MetaScreen *screen) { MetaPluginManager *plugin_mgr; MetaPluginClass *klass; MetaPlugin *plugin; + MetaMonitorManager *monitors; plugin_mgr = g_new0 (MetaPluginManager, 1); plugin_mgr->screen = screen; @@ -101,6 +109,10 @@ meta_plugin_manager_new (MetaScreen *screen) if (klass->start) klass->start (plugin); + monitors = meta_monitor_manager_get (); + g_signal_connect (monitors, "confirm-display-change", + G_CALLBACK (on_confirm_display_change), plugin_mgr); + return plugin_mgr; } @@ -330,3 +342,15 @@ meta_plugin_manager_xevent_filter (MetaPluginManager *plugin_mgr, return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE; } + +void +meta_plugin_manager_confirm_display_change (MetaPluginManager *plugin_mgr) +{ + MetaPlugin *plugin = plugin_mgr->plugin; + MetaPluginClass *klass = META_PLUGIN_GET_CLASS (plugin); + + if (klass->confirm_display_change) + return klass->confirm_display_change (plugin); + else + return meta_plugin_complete_display_change (plugin, TRUE); +} diff --git a/src/compositor/meta-plugin-manager.h b/src/compositor/meta-plugin-manager.h index 9df24c17a..215d450a5 100644 --- a/src/compositor/meta-plugin-manager.h +++ b/src/compositor/meta-plugin-manager.h @@ -73,4 +73,6 @@ gboolean meta_plugin_manager_filter_keybinding (MetaPluginManager *mgr, gboolean meta_plugin_manager_xevent_filter (MetaPluginManager *mgr, XEvent *xev); +void meta_plugin_manager_confirm_display_change (MetaPluginManager *mgr); + #endif diff --git a/src/compositor/meta-plugin.c b/src/compositor/meta-plugin.c index af50cab47..ba8b9cbd9 100644 --- a/src/compositor/meta-plugin.c +++ b/src/compositor/meta-plugin.c @@ -41,6 +41,7 @@ #include "compositor-private.h" #include "meta-window-actor-private.h" +#include "monitor-private.h" G_DEFINE_ABSTRACT_TYPE (MetaPlugin, meta_plugin, G_TYPE_OBJECT); @@ -332,3 +333,13 @@ meta_plugin_get_screen (MetaPlugin *plugin) return priv->screen; } + +void +meta_plugin_complete_display_change (MetaPlugin *plugin, + gboolean ok) +{ + MetaMonitorManager *manager; + + manager = meta_monitor_manager_get (); + meta_monitor_manager_confirm_configuration (manager, ok); +} diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c index 228fb19e0..404302c8b 100644 --- a/src/compositor/plugins/default.c +++ b/src/compositor/plugins/default.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #define _(x) dgettext (GETTEXT_PACKAGE, x) @@ -98,6 +100,8 @@ static void kill_window_effects (MetaPlugin *plugin, MetaWindowActor *actor); static void kill_switch_workspace (MetaPlugin *plugin); +static void confirm_display_change (MetaPlugin *plugin); + static const MetaPluginInfo * plugin_info (MetaPlugin *plugin); META_PLUGIN_DECLARE(MetaDefaultPlugin, meta_default_plugin); @@ -113,6 +117,8 @@ struct _MetaDefaultPluginPrivate ClutterActor *desktop1; ClutterActor *desktop2; + ClutterActor *background_group; + MetaPluginInfo info; }; @@ -203,6 +209,7 @@ meta_default_plugin_class_init (MetaDefaultPluginClass *klass) plugin_class->plugin_info = plugin_info; plugin_class->kill_window_effects = kill_window_effects; plugin_class->kill_switch_workspace = kill_switch_workspace; + plugin_class->confirm_display_change = confirm_display_change; g_type_class_add_private (gobject_class, sizeof (MetaDefaultPluginPrivate)); } @@ -299,9 +306,58 @@ show_stage (MetaPlugin *plugin) return FALSE; } +static void +on_monitors_changed (MetaScreen *screen, + MetaPlugin *plugin) +{ + MetaDefaultPlugin *self = META_DEFAULT_PLUGIN (plugin); + int i, n; + + clutter_actor_destroy_all_children (self->priv->background_group); + + n = meta_screen_get_n_monitors (screen); + for (i = 0; i < n; i++) + { + MetaRectangle rect; + ClutterActor *background; + ClutterColor color; + + meta_screen_get_monitor_geometry (screen, i, &rect); + + background = meta_background_actor_new (); + + clutter_actor_set_position (background, rect.x, rect.y); + clutter_actor_set_size (background, rect.width, rect.height); + + /* Don't use rand() here, mesa calls srand() internally when + parsing the driconf XML, but it's nice if the colors are + reproducible. + */ + clutter_color_init (&color, + g_random_int () % 255, + g_random_int () % 255, + g_random_int () % 255, + 255); + clutter_actor_set_background_color (background, &color); + + clutter_actor_add_child (self->priv->background_group, background); + } +} + static void start (MetaPlugin *plugin) { + MetaDefaultPlugin *self = META_DEFAULT_PLUGIN (plugin); + MetaScreen *screen = meta_plugin_get_screen (plugin); + + self->priv->background_group = meta_background_group_new (); + clutter_actor_insert_child_below (meta_get_window_group_for_screen (screen), + self->priv->background_group, NULL); + + g_signal_connect (screen, "monitors-changed", + G_CALLBACK (on_monitors_changed), plugin); + on_monitors_changed (screen, plugin); + meta_later_add (META_LATER_BEFORE_REDRAW, (GSourceFunc) show_stage, plugin, @@ -782,3 +838,33 @@ plugin_info (MetaPlugin *plugin) return &priv->info; } + +static void +on_dialog_closed (GPid pid, + gint status, + gpointer user_data) +{ + MetaPlugin *plugin = user_data; + gboolean ok; + + ok = g_spawn_check_exit_status (status, NULL); + meta_plugin_complete_display_change (plugin, ok); +} + +static void +confirm_display_change (MetaPlugin *plugin) +{ + GPid pid; + + pid = meta_show_dialog ("--question", + "Does the display look OK?", + "20", + NULL, + "_Keep This Configuration", + "_Restore Previous Configuration", + "preferences-desktop-display", + 0, + NULL, NULL); + + g_child_watch_add (pid, on_dialog_closed, plugin); +} diff --git a/src/core/display.c b/src/core/display.c index 118e60ebf..c0d42d85e 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -2198,6 +2198,7 @@ meta_display_handle_event (MetaDisplay *display, gboolean bypass_compositor; gboolean filter_out_event; XIEvent *input_event; + MetaMonitorManager *monitor; MetaScreen *screen; #ifdef WITH_VERBOSE_MODE @@ -2208,6 +2209,14 @@ meta_display_handle_event (MetaDisplay *display, #ifdef HAVE_STARTUP_NOTIFICATION sn_display_process_event (display->sn_display, event); #endif + + /* Intercept XRandR events early and don't attempt any + processing for them. We still let them through to Gdk though, + so it can update its own internal state. + */ + monitor = meta_monitor_manager_get (); + if (meta_monitor_manager_handle_xevent (monitor, event)) + return FALSE; bypass_compositor = FALSE; filter_out_event = FALSE; @@ -2910,32 +2919,10 @@ meta_display_handle_event (MetaDisplay *display, meta_stack_tracker_configure_event (screen->stack_tracker, &event->xconfigure); } + if (window && window->override_redirect) meta_window_configure_notify (window, &event->xconfigure); - else - /* Handle screen resize */ - { - MetaScreen *screen; - screen = meta_display_screen_for_root (display, - event->xconfigure.window); - - if (screen != NULL) - { -#ifdef HAVE_RANDR - /* do the resize the official way */ - XRRUpdateConfiguration (event); -#else - /* poke around in Xlib */ - screen->xscreen->width = event->xconfigure.width; - screen->xscreen->height = event->xconfigure.height; -#endif - - meta_screen_resize (screen, - event->xconfigure.width, - event->xconfigure.height); - } - } break; case ConfigureRequest: /* This comment and code is found in both twm and fvwm */ diff --git a/src/core/edid-parse.c b/src/core/edid-parse.c new file mode 100644 index 000000000..5b3283ae5 --- /dev/null +++ b/src/core/edid-parse.c @@ -0,0 +1,539 @@ +/* + * Copyright 2007 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Author: Soren Sandmann */ + +#include "edid.h" +#include +#include +#include +#include + +static int +get_bit (int in, int bit) +{ + return (in & (1 << bit)) >> bit; +} + +static int +get_bits (int in, int begin, int end) +{ + int mask = (1 << (end - begin + 1)) - 1; + + return (in >> begin) & mask; +} + +static int +decode_header (const uchar *edid) +{ + if (memcmp (edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0) + return TRUE; + return FALSE; +} + +static int +decode_vendor_and_product_identification (const uchar *edid, MonitorInfo *info) +{ + int is_model_year; + + /* Manufacturer Code */ + info->manufacturer_code[0] = get_bits (edid[0x08], 2, 6); + info->manufacturer_code[1] = get_bits (edid[0x08], 0, 1) << 3; + info->manufacturer_code[1] |= get_bits (edid[0x09], 5, 7); + info->manufacturer_code[2] = get_bits (edid[0x09], 0, 4); + info->manufacturer_code[3] = '\0'; + + info->manufacturer_code[0] += 'A' - 1; + info->manufacturer_code[1] += 'A' - 1; + info->manufacturer_code[2] += 'A' - 1; + + /* Product Code */ + info->product_code = edid[0x0b] << 8 | edid[0x0a]; + + /* Serial Number */ + info->serial_number = + edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | edid[0x0f] << 24; + + /* Week and Year */ + is_model_year = FALSE; + switch (edid[0x10]) + { + case 0x00: + info->production_week = -1; + break; + + case 0xff: + info->production_week = -1; + is_model_year = TRUE; + break; + + default: + info->production_week = edid[0x10]; + break; + } + + if (is_model_year) + { + info->production_year = -1; + info->model_year = 1990 + edid[0x11]; + } + else + { + info->production_year = 1990 + edid[0x11]; + info->model_year = -1; + } + + return TRUE; +} + +static int +decode_edid_version (const uchar *edid, MonitorInfo *info) +{ + info->major_version = edid[0x12]; + info->minor_version = edid[0x13]; + + return TRUE; +} + +static int +decode_display_parameters (const uchar *edid, MonitorInfo *info) +{ + /* Digital vs Analog */ + info->is_digital = get_bit (edid[0x14], 7); + + if (info->is_digital) + { + int bits; + + static const int bit_depth[8] = + { + -1, 6, 8, 10, 12, 14, 16, -1 + }; + + static const Interface interfaces[6] = + { + UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT + }; + + bits = get_bits (edid[0x14], 4, 6); + info->connector.digital.bits_per_primary = bit_depth[bits]; + + bits = get_bits (edid[0x14], 0, 3); + + if (bits <= 5) + info->connector.digital.interface = interfaces[bits]; + else + info->connector.digital.interface = UNDEFINED; + } + else + { + int bits = get_bits (edid[0x14], 5, 6); + + static const double levels[][3] = + { + { 0.7, 0.3, 1.0 }, + { 0.714, 0.286, 1.0 }, + { 1.0, 0.4, 1.4 }, + { 0.7, 0.0, 0.7 }, + }; + + info->connector.analog.video_signal_level = levels[bits][0]; + info->connector.analog.sync_signal_level = levels[bits][1]; + info->connector.analog.total_signal_level = levels[bits][2]; + + info->connector.analog.blank_to_black = get_bit (edid[0x14], 4); + + info->connector.analog.separate_hv_sync = get_bit (edid[0x14], 3); + info->connector.analog.composite_sync_on_h = get_bit (edid[0x14], 2); + info->connector.analog.composite_sync_on_green = get_bit (edid[0x14], 1); + + info->connector.analog.serration_on_vsync = get_bit (edid[0x14], 0); + } + + /* Screen Size / Aspect Ratio */ + if (edid[0x15] == 0 && edid[0x16] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = -1.0; + } + else if (edid[0x16] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = 100.0 / (edid[0x15] + 99); + } + else if (edid[0x15] == 0) + { + info->width_mm = -1; + info->height_mm = -1; + info->aspect_ratio = 100.0 / (edid[0x16] + 99); + info->aspect_ratio = 1/info->aspect_ratio; /* portrait */ + } + else + { + info->width_mm = 10 * edid[0x15]; + info->height_mm = 10 * edid[0x16]; + } + + /* Gamma */ + if (edid[0x17] == 0xFF) + info->gamma = -1.0; + else + info->gamma = (edid[0x17] + 100.0) / 100.0; + + /* Features */ + info->standby = get_bit (edid[0x18], 7); + info->suspend = get_bit (edid[0x18], 6); + info->active_off = get_bit (edid[0x18], 5); + + if (info->is_digital) + { + info->connector.digital.rgb444 = TRUE; + if (get_bit (edid[0x18], 3)) + info->connector.digital.ycrcb444 = 1; + if (get_bit (edid[0x18], 4)) + info->connector.digital.ycrcb422 = 1; + } + else + { + int bits = get_bits (edid[0x18], 3, 4); + ColorType color_type[4] = + { + MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR + }; + + info->connector.analog.color_type = color_type[bits]; + } + + info->srgb_is_standard = get_bit (edid[0x18], 2); + + /* In 1.3 this is called "has preferred timing" */ + info->preferred_timing_includes_native = get_bit (edid[0x18], 1); + + /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */ + info->continuous_frequency = get_bit (edid[0x18], 0); + return TRUE; +} + +static double +decode_fraction (int high, int low) +{ + double result = 0.0; + int i; + + high = (high << 2) | low; + + for (i = 0; i < 10; ++i) + result += get_bit (high, i) * pow (2, i - 10); + + return result; +} + +static int +decode_color_characteristics (const uchar *edid, MonitorInfo *info) +{ + info->red_x = decode_fraction (edid[0x1b], get_bits (edid[0x19], 6, 7)); + info->red_y = decode_fraction (edid[0x1c], get_bits (edid[0x19], 5, 4)); + info->green_x = decode_fraction (edid[0x1d], get_bits (edid[0x19], 2, 3)); + info->green_y = decode_fraction (edid[0x1e], get_bits (edid[0x19], 0, 1)); + info->blue_x = decode_fraction (edid[0x1f], get_bits (edid[0x1a], 6, 7)); + info->blue_y = decode_fraction (edid[0x20], get_bits (edid[0x1a], 4, 5)); + info->white_x = decode_fraction (edid[0x21], get_bits (edid[0x1a], 2, 3)); + info->white_y = decode_fraction (edid[0x22], get_bits (edid[0x1a], 0, 1)); + + return TRUE; +} + +static int +decode_established_timings (const uchar *edid, MonitorInfo *info) +{ + static const Timing established[][8] = + { + { + { 800, 600, 60 }, + { 800, 600, 56 }, + { 640, 480, 75 }, + { 640, 480, 72 }, + { 640, 480, 67 }, + { 640, 480, 60 }, + { 720, 400, 88 }, + { 720, 400, 70 } + }, + { + { 1280, 1024, 75 }, + { 1024, 768, 75 }, + { 1024, 768, 70 }, + { 1024, 768, 60 }, + { 1024, 768, 87 }, + { 832, 624, 75 }, + { 800, 600, 75 }, + { 800, 600, 72 } + }, + { + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 1152, 870, 75 } + }, + }; + + int i, j, idx; + + idx = 0; + for (i = 0; i < 3; ++i) + { + for (j = 0; j < 8; ++j) + { + int byte = edid[0x23 + i]; + + if (get_bit (byte, j) && established[i][j].frequency != 0) + info->established[idx++] = established[i][j]; + } + } + return TRUE; +} + +static int +decode_standard_timings (const uchar *edid, MonitorInfo *info) +{ + int i; + + for (i = 0; i < 8; i++) + { + int first = edid[0x26 + 2 * i]; + int second = edid[0x27 + 2 * i]; + + if (first != 0x01 && second != 0x01) + { + int w = 8 * (first + 31); + int h = 0; + + switch (get_bits (second, 6, 7)) + { + case 0x00: h = (w / 16) * 10; break; + case 0x01: h = (w / 4) * 3; break; + case 0x02: h = (w / 5) * 4; break; + case 0x03: h = (w / 16) * 9; break; + } + + info->standard[i].width = w; + info->standard[i].height = h; + info->standard[i].frequency = get_bits (second, 0, 5) + 60; + } + } + + return TRUE; +} + +static void +decode_lf_string (const uchar *s, int n_chars, char *result) +{ + int i; + for (i = 0; i < n_chars; ++i) + { + if (s[i] == 0x0a) + { + *result++ = '\0'; + break; + } + else if (s[i] == 0x00) + { + /* Convert embedded 0's to spaces */ + *result++ = ' '; + } + else + { + *result++ = s[i]; + } + } +} + +static void +decode_display_descriptor (const uchar *desc, + MonitorInfo *info) +{ + switch (desc[0x03]) + { + case 0xFC: + decode_lf_string (desc + 5, 13, info->dsc_product_name); + break; + case 0xFF: + decode_lf_string (desc + 5, 13, info->dsc_serial_number); + break; + case 0xFE: + decode_lf_string (desc + 5, 13, info->dsc_string); + break; + case 0xFD: + /* Range Limits */ + break; + case 0xFB: + /* Color Point */ + break; + case 0xFA: + /* Timing Identifications */ + break; + case 0xF9: + /* Color Management */ + break; + case 0xF8: + /* Timing Codes */ + break; + case 0xF7: + /* Established Timings */ + break; + case 0x10: + break; + } +} + +static void +decode_detailed_timing (const uchar *timing, + DetailedTiming *detailed) +{ + int bits; + StereoType stereo[] = + { + NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT, + TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN, + FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE + }; + + detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000; + detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4); + detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8); + detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4); + detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8); + detailed->h_front_porch = timing[0x08] | get_bits (timing[0x0b], 6, 7) << 8; + detailed->h_sync = timing[0x09] | get_bits (timing[0x0b], 4, 5) << 8; + detailed->v_front_porch = + get_bits (timing[0x0a], 4, 7) | get_bits (timing[0x0b], 2, 3) << 4; + detailed->v_sync = + get_bits (timing[0x0a], 0, 3) | get_bits (timing[0x0b], 0, 1) << 4; + detailed->width_mm = timing[0x0c] | get_bits (timing[0x0e], 4, 7) << 8; + detailed->height_mm = timing[0x0d] | get_bits (timing[0x0e], 0, 3) << 8; + detailed->right_border = timing[0x0f]; + detailed->top_border = timing[0x10]; + + detailed->interlaced = get_bit (timing[0x11], 7); + + /* Stereo */ + bits = get_bits (timing[0x11], 5, 6) << 1 | get_bit (timing[0x11], 0); + detailed->stereo = stereo[bits]; + + /* Sync */ + bits = timing[0x11]; + + detailed->digital_sync = get_bit (bits, 4); + if (detailed->digital_sync) + { + detailed->connector.digital.composite = !get_bit (bits, 3); + + if (detailed->connector.digital.composite) + { + detailed->connector.digital.serrations = get_bit (bits, 2); + detailed->connector.digital.negative_vsync = FALSE; + } + else + { + detailed->connector.digital.serrations = FALSE; + detailed->connector.digital.negative_vsync = !get_bit (bits, 2); + } + + detailed->connector.digital.negative_hsync = !get_bit (bits, 0); + } + else + { + detailed->connector.analog.bipolar = get_bit (bits, 3); + detailed->connector.analog.serrations = get_bit (bits, 2); + detailed->connector.analog.sync_on_green = !get_bit (bits, 1); + } +} + +static int +decode_descriptors (const uchar *edid, MonitorInfo *info) +{ + int i; + int timing_idx; + + timing_idx = 0; + + for (i = 0; i < 4; ++i) + { + int index = 0x36 + i * 18; + + if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00) + { + decode_display_descriptor (edid + index, info); + } + else + { + decode_detailed_timing (edid + index, &(info->detailed_timings[timing_idx++])); + } + } + + info->n_detailed_timings = timing_idx; + + return TRUE; +} + +static void +decode_check_sum (const uchar *edid, + MonitorInfo *info) +{ + int i; + uchar check = 0; + + for (i = 0; i < 128; ++i) + check += edid[i]; + + info->checksum = check; +} + +MonitorInfo * +decode_edid (const uchar *edid) +{ + MonitorInfo *info = g_new0 (MonitorInfo, 1); + + decode_check_sum (edid, info); + + if (decode_header (edid) + && decode_vendor_and_product_identification (edid, info) + && decode_edid_version (edid, info) + && decode_display_parameters (edid, info) + && decode_color_characteristics (edid, info) + && decode_established_timings (edid, info) + && decode_standard_timings (edid, info) + && decode_descriptors (edid, info)) + { + return info; + } + else + { + g_free (info); + return NULL; + } +} diff --git a/src/core/edid.h b/src/core/edid.h new file mode 100644 index 000000000..703c639ee --- /dev/null +++ b/src/core/edid.h @@ -0,0 +1,195 @@ +/* edid.h + * + * Copyright 2007, 2008, Red Hat, Inc. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Soren Sandmann + */ + +#ifndef EDID_H +#define EDID_H + +typedef unsigned char uchar; +typedef struct MonitorInfo MonitorInfo; +typedef struct Timing Timing; +typedef struct DetailedTiming DetailedTiming; + +typedef enum +{ + UNDEFINED, + DVI, + HDMI_A, + HDMI_B, + MDDI, + DISPLAY_PORT +} Interface; + +typedef enum +{ + UNDEFINED_COLOR, + MONOCHROME, + RGB, + OTHER_COLOR +} ColorType; + +typedef enum +{ + NO_STEREO, + FIELD_RIGHT, + FIELD_LEFT, + TWO_WAY_RIGHT_ON_EVEN, + TWO_WAY_LEFT_ON_EVEN, + FOUR_WAY_INTERLEAVED, + SIDE_BY_SIDE +} StereoType; + +struct Timing +{ + int width; + int height; + int frequency; +}; + +struct DetailedTiming +{ + int pixel_clock; + int h_addr; + int h_blank; + int h_sync; + int h_front_porch; + int v_addr; + int v_blank; + int v_sync; + int v_front_porch; + int width_mm; + int height_mm; + int right_border; + int top_border; + int interlaced; + StereoType stereo; + + int digital_sync; + union + { + struct + { + int bipolar; + int serrations; + int sync_on_green; + } analog; + + struct + { + int composite; + int serrations; + int negative_vsync; + int negative_hsync; + } digital; + } connector; +}; + +struct MonitorInfo +{ + int checksum; + char manufacturer_code[4]; + int product_code; + unsigned int serial_number; + + int production_week; /* -1 if not specified */ + int production_year; /* -1 if not specified */ + int model_year; /* -1 if not specified */ + + int major_version; + int minor_version; + + int is_digital; + + union + { + struct + { + int bits_per_primary; + Interface interface; + int rgb444; + int ycrcb444; + int ycrcb422; + } digital; + + struct + { + double video_signal_level; + double sync_signal_level; + double total_signal_level; + + int blank_to_black; + + int separate_hv_sync; + int composite_sync_on_h; + int composite_sync_on_green; + int serration_on_vsync; + ColorType color_type; + } analog; + } connector; + + int width_mm; /* -1 if not specified */ + int height_mm; /* -1 if not specified */ + double aspect_ratio; /* -1.0 if not specififed */ + + double gamma; /* -1.0 if not specified */ + + int standby; + int suspend; + int active_off; + + int srgb_is_standard; + int preferred_timing_includes_native; + int continuous_frequency; + + double red_x; + double red_y; + double green_x; + double green_y; + double blue_x; + double blue_y; + double white_x; + double white_y; + + Timing established[24]; /* Terminated by 0x0x0 */ + Timing standard[8]; + + int n_detailed_timings; + DetailedTiming detailed_timings[4]; /* If monitor has a preferred + * mode, it is the first one + * (whether it has, is + * determined by the + * preferred_timing_includes + * bit. + */ + + /* Optional product description */ + char dsc_serial_number[14]; + char dsc_product_name[14]; + char dsc_string[14]; /* Unspecified ASCII data */ +}; + +MonitorInfo *decode_edid (const uchar *data); +char *make_display_name (const MonitorInfo *info); +char *make_display_size_string (int width_mm, int height_mm); + +#endif diff --git a/src/core/keybindings.c b/src/core/keybindings.c index 8439ea830..7f58334f8 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -1965,6 +1965,23 @@ process_overlay_key (MetaDisplay *display, return TRUE; meta_display_overlay_key_activate (display); } + else + { + /* In some rare race condition, mutter might not receive the Super_L + * KeyRelease event because: + * - the compositor might end the modal mode and call XIUngrabDevice + * while the key is still down + * - passive grabs are only activated on KeyPress and not KeyRelease. + * + * In this case, display->overlay_key_only_pressed might be wrong. + * Mutter still ought to acknowledge events, otherwise the X server + * will not send the next events. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=666101 + */ + XIAllowEvents (display->xdisplay, event->deviceid, + XIAsyncDevice, event->time); + } return TRUE; } diff --git a/src/core/meta-xrandr-shared.h b/src/core/meta-xrandr-shared.h new file mode 100644 index 000000000..c98f58103 --- /dev/null +++ b/src/core/meta-xrandr-shared.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2013 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* This file is shared between mutter (src/core/meta-xrandr-shared.h) + and gnome-desktop (libgnome-desktop/meta-xrandr-shared.h). + + The canonical place for all changes is mutter. + + There should be no includes in this file. +*/ + +#ifndef META_XRANDR_SHARED_H +#define META_XRANDR_SHARED_H + +typedef enum { + META_POWER_SAVE_UNKNOWN = -1, + META_POWER_SAVE_ON = 0, + META_POWER_SAVE_STANDBY, + META_POWER_SAVE_SUSPEND, + META_POWER_SAVE_OFF, +} MetaPowerSave; + +#endif diff --git a/src/core/monitor-config.c b/src/core/monitor-config.c new file mode 100644 index 000000000..420c670b6 --- /dev/null +++ b/src/core/monitor-config.c @@ -0,0 +1,1769 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001, 2002 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat Inc. + * Some ICCCM manager selection code derived from fvwm2, + * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 Elijah Newren + * Copyright (C) 2013 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* + * Portions of this file are derived from gnome-desktop/libgnome-desktop/gnome-rr-config.c + * + * Copyright 2007, 2008, Red Hat, Inc. + * Copyright 2010 Giovanni Campagna + * + * Author: Soren Sandmann + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include "monitor-private.h" + +/* These structures represent the intended/persistent configuration, + as stored in the monitors.xml file. +*/ + +typedef struct { + char *connector; + char *vendor; + char *product; + char *serial; +} MetaOutputKey; + +typedef struct { + gboolean enabled; + MetaRectangle rect; + float refresh_rate; + enum wl_output_transform transform; + + gboolean is_primary; + gboolean is_presentation; +} MetaOutputConfig; + +typedef struct { + MetaOutputKey *keys; + MetaOutputConfig *outputs; + unsigned int n_outputs; +} MetaConfiguration; + +struct _MetaMonitorConfig { + GObject parent_instance; + + GHashTable *configs; + MetaConfiguration *current; + gboolean current_is_stored; + MetaConfiguration *previous; + + GFile *file; + GCancellable *save_cancellable; + + UpClient *up_client; + gboolean lid_is_closed; +}; + +struct _MetaMonitorConfigClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (MetaMonitorConfig, meta_monitor_config, G_TYPE_OBJECT); + +static gboolean meta_monitor_config_assign_crtcs (MetaConfiguration *config, + MetaMonitorManager *manager, + GPtrArray *crtcs, + GPtrArray *outputs); + +static void power_client_changed_cb (UpClient *client, + gpointer user_data); + +static void +free_output_key (MetaOutputKey *key) +{ + g_free (key->connector); + g_free (key->vendor); + g_free (key->product); + g_free (key->serial); +} + +static void +config_clear (MetaConfiguration *config) +{ + unsigned int i; + + for (i = 0; i < config->n_outputs; i++) + free_output_key (&config->keys[i]); + + g_free (config->keys); + g_free (config->outputs); +} + +static void +config_free (gpointer config) +{ + config_clear (config); + g_slice_free (MetaConfiguration, config); +} + +static unsigned long +output_key_hash (const MetaOutputKey *key) +{ + return g_str_hash (key->connector) ^ + g_str_hash (key->vendor) ^ + g_str_hash (key->product) ^ + g_str_hash (key->serial); +} + +static gboolean +output_key_equal (const MetaOutputKey *one, + const MetaOutputKey *two) +{ + return strcmp (one->connector, two->connector) == 0 && + strcmp (one->vendor, two->vendor) == 0 && + strcmp (one->product, two->product) == 0 && + strcmp (one->serial, two->serial) == 0; +} + +static unsigned int +config_hash (gconstpointer data) +{ + const MetaConfiguration *config = data; + unsigned int i, hash; + + hash = 0; + for (i = 0; i < config->n_outputs; i++) + hash ^= output_key_hash (&config->keys[i]); + + return hash; +} + +static gboolean +config_equal (gconstpointer one, + gconstpointer two) +{ + const MetaConfiguration *c_one = one; + const MetaConfiguration *c_two = two; + unsigned int i; + gboolean ok; + + if (c_one->n_outputs != c_two->n_outputs) + return FALSE; + + ok = TRUE; + for (i = 0; i < c_one->n_outputs && ok; i++) + ok = output_key_equal (&c_one->keys[i], + &c_two->keys[i]); + + return ok; +} + +static void +meta_monitor_config_init (MetaMonitorConfig *self) +{ + const char *filename; + char *path; + + self->configs = g_hash_table_new_full (config_hash, config_equal, NULL, config_free); + + filename = g_getenv ("MUTTER_MONITOR_FILENAME"); + if (filename == NULL) + filename = "monitors.xml"; + + path = g_build_filename (g_get_user_config_dir (), filename, NULL); + self->file = g_file_new_for_path (path); + g_free (path); + + self->up_client = up_client_new (); + self->lid_is_closed = up_client_get_lid_is_closed (self->up_client); + + g_signal_connect_object (self->up_client, "changed", + G_CALLBACK (power_client_changed_cb), self, 0); +} + +static void +meta_monitor_config_finalize (GObject *object) +{ + MetaMonitorConfig *self = META_MONITOR_CONFIG (object); + + g_hash_table_destroy (self->configs); +} + +static void +meta_monitor_config_class_init (MetaMonitorConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meta_monitor_config_finalize; +} + +typedef enum { + STATE_INITIAL, + STATE_MONITORS, + STATE_CONFIGURATION, + STATE_OUTPUT, + STATE_OUTPUT_FIELD, + STATE_CLONE +} ParserState; + +typedef struct { + MetaMonitorConfig *config; + ParserState state; + int unknown_count; + + GArray *key_array; + GArray *output_array; + MetaOutputKey key; + MetaOutputConfig output; + + char *output_field; +} ConfigParser; + +static void +handle_start_element (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_INITIAL: + { + char *version; + + if (strcmp (element_name, "monitors") != 0) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid document element %s", element_name); + return; + } + + if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, + error, + G_MARKUP_COLLECT_STRING, "version", &version, + G_MARKUP_COLLECT_INVALID)) + return; + + if (strcmp (version, "1") != 0) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Invalid or unsupported version %s", version); + return; + } + + parser->state = STATE_MONITORS; + return; + } + + case STATE_MONITORS: + { + if (strcmp (element_name, "configuration") != 0) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid toplevel element %s", element_name); + return; + } + + parser->key_array = g_array_new (FALSE, FALSE, sizeof (MetaOutputKey)); + parser->output_array = g_array_new (FALSE, FALSE, sizeof (MetaOutputConfig)); + parser->state = STATE_CONFIGURATION; + return; + } + + case STATE_CONFIGURATION: + { + if (strcmp (element_name, "clone") == 0 && parser->unknown_count == 0) + { + parser->state = STATE_CLONE; + } + else if (strcmp (element_name, "output") == 0 && parser->unknown_count == 0) + { + char *name; + + if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, + error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_INVALID)) + return; + + memset (&parser->key, 0, sizeof (MetaOutputKey)); + memset (&parser->output, 0, sizeof (MetaOutputConfig)); + + parser->key.connector = g_strdup (name); + parser->state = STATE_OUTPUT; + } + else + { + parser->unknown_count++; + } + + return; + } + + case STATE_OUTPUT: + { + if ((strcmp (element_name, "vendor") == 0 || + strcmp (element_name, "product") == 0 || + strcmp (element_name, "serial") == 0 || + strcmp (element_name, "width") == 0 || + strcmp (element_name, "height") == 0 || + strcmp (element_name, "rate") == 0 || + strcmp (element_name, "x") == 0 || + strcmp (element_name, "y") == 0 || + strcmp (element_name, "rotation") == 0 || + strcmp (element_name, "reflect_x") == 0 || + strcmp (element_name, "reflect_y") == 0 || + strcmp (element_name, "primary") == 0 || + strcmp (element_name, "presentation") == 0) && parser->unknown_count == 0) + { + parser->state = STATE_OUTPUT_FIELD; + + parser->output_field = g_strdup (element_name); + } + else + { + parser->unknown_count++; + } + + return; + } + + case STATE_CLONE: + case STATE_OUTPUT_FIELD: + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected element %s", element_name); + return; + } + + default: + g_assert_not_reached (); + } +} + +static void +handle_end_element (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_MONITORS: + { + parser->state = STATE_INITIAL; + return; + } + + case STATE_CONFIGURATION: + { + if (strcmp (element_name, "configuration") == 0 && parser->unknown_count == 0) + { + MetaConfiguration *config = g_slice_new (MetaConfiguration); + + g_assert (parser->key_array->len == parser->output_array->len); + + config->n_outputs = parser->key_array->len; + config->keys = (void*)g_array_free (parser->key_array, FALSE); + config->outputs = (void*)g_array_free (parser->output_array, FALSE); + + g_hash_table_replace (parser->config->configs, config, config); + + parser->key_array = NULL; + parser->output_array = NULL; + parser->state = STATE_MONITORS; + } + else + { + parser->unknown_count--; + + g_assert (parser->unknown_count >= 0); + } + + return; + } + + case STATE_OUTPUT: + { + if (strcmp (element_name, "output") == 0 && parser->unknown_count == 0) + { + if (parser->key.vendor == NULL || + parser->key.product == NULL || + parser->key.serial == NULL) + { + /* Disconnected output, ignore */ + free_output_key (&parser->key); + } + else + { + if (parser->output.rect.width == 0 && + parser->output.rect.width == 0) + parser->output.enabled = FALSE; + else + parser->output.enabled = TRUE; + + g_array_append_val (parser->key_array, parser->key); + g_array_append_val (parser->output_array, parser->output); + } + + memset (&parser->key, 0, sizeof (MetaOutputKey)); + memset (&parser->output, 0, sizeof (MetaOutputConfig)); + + parser->state = STATE_CONFIGURATION; + } + else + { + parser->unknown_count--; + + g_assert (parser->unknown_count >= 0); + } + + return; + } + + case STATE_CLONE: + { + parser->state = STATE_CONFIGURATION; + return; + } + + case STATE_OUTPUT_FIELD: + { + g_free (parser->output_field); + parser->output_field = NULL; + + parser->state = STATE_OUTPUT; + return; + } + + case STATE_INITIAL: + default: + g_assert_not_reached (); + } +} + +static void +read_int (const char *text, + gsize text_len, + gint *field, + GError **error) +{ + char buf[64]; + gint64 v; + char *end; + + strncpy (buf, text, text_len); + buf[MIN (63, text_len)] = 0; + + v = g_ascii_strtoll (buf, &end, 10); + + /* Limit reasonable values (actual limits are a lot smaller that these) */ + if (*end || v < 0 || v > G_MAXINT16) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Expected a number, got %s", buf); + else + *field = v; +} + +static void +read_float (const char *text, + gsize text_len, + gfloat *field, + GError **error) +{ + char buf[64]; + gfloat v; + char *end; + + strncpy (buf, text, text_len); + buf[MIN (63, text_len)] = 0; + + v = g_ascii_strtod (buf, &end); + + /* Limit reasonable values (actual limits are a lot smaller that these) */ + if (*end) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Expected a number, got %s", buf); + else + *field = v; +} + +static gboolean +read_bool (const char *text, + gsize text_len, + GError **error) +{ + if (strncmp (text, "no", text_len) == 0) + return FALSE; + else if (strncmp (text, "yes", text_len) == 0) + return TRUE; + else + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Invalid boolean value %.*s", (int)text_len, text); + + return FALSE; +} + +static gboolean +is_all_whitespace (const char *text, + gsize text_len) +{ + gsize i; + + for (i = 0; i < text_len; i++) + if (!g_ascii_isspace (text[i])) + return FALSE; + + return TRUE; +} + +static void +handle_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_MONITORS: + { + if (!is_all_whitespace (text, text_len)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected content at this point"); + return; + } + + case STATE_CONFIGURATION: + { + if (parser->unknown_count == 0) + { + if (!is_all_whitespace (text, text_len)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected content at this point"); + } + else + { + /* Handling unknown element, ignore */ + } + + return; + } + + case STATE_OUTPUT: + { + if (parser->unknown_count == 0) + { + if (!is_all_whitespace (text, text_len)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected content at this point"); + } + else + { + /* Handling unknown element, ignore */ + } + return; + } + + case STATE_CLONE: + { + /* Ignore the clone flag */ + return; + } + + case STATE_OUTPUT_FIELD: + { + if (strcmp (parser->output_field, "vendor") == 0) + parser->key.vendor = g_strndup (text, text_len); + else if (strcmp (parser->output_field, "product") == 0) + parser->key.product = g_strndup (text, text_len); + else if (strcmp (parser->output_field, "serial") == 0) + parser->key.serial = g_strndup (text, text_len); + else if (strcmp (parser->output_field, "width") == 0) + read_int (text, text_len, &parser->output.rect.width, error); + else if (strcmp (parser->output_field, "height") == 0) + read_int (text, text_len, &parser->output.rect.height, error); + else if (strcmp (parser->output_field, "rate") == 0) + read_float (text, text_len, &parser->output.refresh_rate, error); + else if (strcmp (parser->output_field, "x") == 0) + read_int (text, text_len, &parser->output.rect.x, error); + else if (strcmp (parser->output_field, "y") == 0) + read_int (text, text_len, &parser->output.rect.y, error); + else if (strcmp (parser->output_field, "rotation") == 0) + { + if (strncmp (text, "normal", text_len) == 0) + parser->output.transform = WL_OUTPUT_TRANSFORM_NORMAL; + else if (strncmp (text, "left", text_len) == 0) + parser->output.transform = WL_OUTPUT_TRANSFORM_90; + else if (strncmp (text, "upside_down", text_len) == 0) + parser->output.transform = WL_OUTPUT_TRANSFORM_180; + else if (strncmp (text, "right", text_len) == 0) + parser->output.transform = WL_OUTPUT_TRANSFORM_270; + else + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Invalid rotation type %.*s", (int)text_len, text); + } + else if (strcmp (parser->output_field, "reflect_x") == 0) + parser->output.transform += read_bool (text, text_len, error) ? + WL_OUTPUT_TRANSFORM_FLIPPED : 0; + else if (strcmp (parser->output_field, "reflect_y") == 0) + { + /* FIXME (look at the rotation map in monitor.c) */ + if (read_bool (text, text_len, error)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Y reflection is not supported"); + } + else if (strcmp (parser->output_field, "primary") == 0) + parser->output.is_primary = read_bool (text, text_len, error); + else if (strcmp (parser->output_field, "presentation") == 0) + parser->output.is_presentation = read_bool (text, text_len, error); + else + g_assert_not_reached (); + return; + } + + case STATE_INITIAL: + default: + g_assert_not_reached (); + } +} + +static const GMarkupParser config_parser = { + .start_element = handle_start_element, + .end_element = handle_end_element, + .text = handle_text, +}; + +static void +meta_monitor_config_load (MetaMonitorConfig *self) +{ + char *contents; + gsize size; + gboolean ok; + GError *error; + GMarkupParseContext *context; + ConfigParser parser; + + /* Note: we're explicitly loading this file synchronously because + we don't want to leave the default configuration on for even a frame, ie we + want atomic modeset as much as possible. + + This function is called only at early initialization anyway, before + we connect to X or create the wayland socket. + */ + + error = NULL; + ok = g_file_load_contents (self->file, NULL, &contents, &size, NULL, &error); + if (!ok) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + meta_warning ("Failed to load stored monitor configuration: %s\n", error->message); + + g_error_free (error); + return; + } + + memset (&parser, 0, sizeof (ConfigParser)); + parser.config = self; + parser.state = STATE_INITIAL; + + context = g_markup_parse_context_new (&config_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT | + G_MARKUP_PREFIX_ERROR_POSITION, + &parser, NULL); + ok = g_markup_parse_context_parse (context, contents, size, &error); + + if (!ok) + { + meta_warning ("Failed to parse stored monitor configuration: %s\n", error->message); + + g_error_free (error); + + if (parser.key_array) + g_array_free (parser.key_array, TRUE); + if (parser.output_array) + g_array_free (parser.output_array, TRUE); + + free_output_key (&parser.key); + } +} + +MetaMonitorConfig * +meta_monitor_config_new (void) +{ + MetaMonitorConfig *self; + + self = g_object_new (META_TYPE_MONITOR_CONFIG, NULL); + meta_monitor_config_load (self); + + return self; +} + +static void +init_key_from_output (MetaOutputKey *key, + MetaOutput *output) +{ + key->connector = g_strdup (output->name); + key->product = g_strdup (output->product); + key->vendor = g_strdup (output->vendor); + key->serial = g_strdup (output->serial); +} + +static void +make_config_key (MetaConfiguration *key, + MetaOutput *outputs, + unsigned n_outputs, + unsigned skip) +{ + unsigned int o, i; + + key->outputs = NULL; + key->keys = g_new0 (MetaOutputKey, n_outputs); + + for (o = 0, i = 0; i < n_outputs; o++, i++) + if (i == skip) + o--; + else + init_key_from_output (&key->keys[o], &outputs[i]); + + key->n_outputs = o; +} + +gboolean +meta_monitor_config_match_current (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + MetaOutput *outputs; + unsigned n_outputs; + MetaConfiguration key; + gboolean ok; + + if (self->current == NULL) + return FALSE; + + outputs = meta_monitor_manager_get_outputs (manager, &n_outputs); + + make_config_key (&key, outputs, n_outputs, -1); + ok = config_equal (&key, self->current); + + config_clear (&key); + return ok; +} + +static MetaConfiguration * +meta_monitor_config_get_stored (MetaMonitorConfig *self, + MetaOutput *outputs, + unsigned n_outputs) +{ + MetaConfiguration key; + MetaConfiguration *stored; + + make_config_key (&key, outputs, n_outputs, -1); + stored = g_hash_table_lookup (self->configs, &key); + + config_clear (&key); + return stored; +} + +static gboolean +apply_configuration (MetaMonitorConfig *self, + MetaConfiguration *config, + MetaMonitorManager *manager, + gboolean stored) +{ + GPtrArray *crtcs, *outputs; + + crtcs = g_ptr_array_new_full (config->n_outputs, (GDestroyNotify)meta_crtc_info_free); + outputs = g_ptr_array_new_full (config->n_outputs, (GDestroyNotify)meta_output_info_free); + + if (!meta_monitor_config_assign_crtcs (config, manager, crtcs, outputs)) + { + g_ptr_array_unref (crtcs); + g_ptr_array_unref (outputs); + if (!stored) + config_free (config); + + return FALSE; + } + + meta_monitor_manager_apply_configuration (manager, + (MetaCRTCInfo**)crtcs->pdata, crtcs->len, + (MetaOutputInfo**)outputs->pdata, outputs->len); + + /* Stored (persistent) configurations override the previous one always. + Also, we clear the previous configuration if the current one (which is + about to become previous) is stored. + */ + if (stored || + (self->current && self->current_is_stored)) + { + if (self->previous) + config_free (self->previous); + self->previous = NULL; + } + else + { + self->previous = self->current; + } + + self->current = config; + self->current_is_stored = stored; + + if (self->current == self->previous) + self->previous = NULL; + + g_ptr_array_unref (crtcs); + g_ptr_array_unref (outputs); + return TRUE; +} + +static gboolean +key_is_laptop (MetaOutputKey *key) +{ + /* FIXME: extend with better heuristics */ + return g_str_has_prefix (key->connector, "LVDS") || + g_str_has_prefix (key->connector, "eDP"); +} + +static gboolean +laptop_display_is_on (MetaConfiguration *config) +{ + unsigned int i; + + for (i = 0; i < config->n_outputs; i++) + { + MetaOutputKey *key = &config->keys[i]; + MetaOutputConfig *output = &config->outputs[i]; + + if (key_is_laptop (key) && output->enabled) + return TRUE; + } + + return FALSE; +} + +static MetaConfiguration * +make_laptop_lid_config (MetaConfiguration *reference) +{ + MetaConfiguration *new; + unsigned int i; + gboolean has_primary; + + g_assert (reference->n_outputs > 1); + + new = g_slice_new0 (MetaConfiguration); + new->n_outputs = reference->n_outputs; + new->keys = g_new0 (MetaOutputKey, reference->n_outputs); + new->outputs = g_new0 (MetaOutputConfig, reference->n_outputs); + + for (i = 0; i < new->n_outputs; i++) + { + MetaOutputKey *current_key = &reference->keys[i]; + MetaOutputConfig *current_output = &reference->outputs[i]; + + new->keys[i].connector = g_strdup (current_key->connector); + new->keys[i].vendor = g_strdup (current_key->vendor); + new->keys[i].product = g_strdup (current_key->product); + new->keys[i].serial = g_strdup (current_key->serial); + + if (g_str_has_prefix (current_key->connector, "LVDS") || + g_str_has_prefix (current_key->connector, "eDP")) + new->outputs[i].enabled = FALSE; + else + /* This can potentially leave a "hole" in the screen, + but this is actually a good thing, as it means windows + don't move around. + */ + new->outputs[i] = *current_output; + } + + has_primary = FALSE; + for (i = 0; i < new->n_outputs; i++) + { + if (new->outputs[i].is_primary) + { + has_primary = TRUE; + break; + } + } + if (!has_primary) + new->outputs[0].is_primary = TRUE; + + return new; +} + +gboolean +meta_monitor_config_apply_stored (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + MetaOutput *outputs; + MetaConfiguration *stored; + unsigned n_outputs; + + outputs = meta_monitor_manager_get_outputs (manager, &n_outputs); + stored = meta_monitor_config_get_stored (self, outputs, n_outputs); + + if (stored) + { + if (self->lid_is_closed && + stored->n_outputs > 1 && + laptop_display_is_on (stored)) + return apply_configuration (self, make_laptop_lid_config (stored), + manager, FALSE); + else + return apply_configuration (self, stored, manager, TRUE); + } + else + return FALSE; +} + +/* + * Tries to find the primary output according to the current layout, + * or failing that, an output that is good to be a primary (LVDS or eDP, + * which are internal monitors), or failing that, the one with the + * best resolution + */ +static MetaOutput * +find_primary_output (MetaOutput *outputs, + unsigned n_outputs) +{ + unsigned i; + MetaOutput *best; + int best_width, best_height; + + g_assert (n_outputs >= 1); + + for (i = 0; i < n_outputs; i++) + { + if (outputs[i].is_primary) + return &outputs[i]; + } + + for (i = 0; i < n_outputs; i++) + { + if (g_str_has_prefix (outputs[i].name, "LVDS") || + g_str_has_prefix (outputs[i].name, "eDP")) + return &outputs[i]; + } + + best = NULL; + best_width = 0; best_height = 0; + for (i = 0; i < n_outputs; i++) + { + if (outputs[i].preferred_mode->width * outputs[i].preferred_mode->height > + best_width * best_height) + { + best = &outputs[i]; + best_width = outputs[i].preferred_mode->width; + best_height = outputs[i].preferred_mode->height; + } + } + + return best; +} + +static MetaConfiguration * +make_default_config (MetaMonitorConfig *self, + MetaOutput *outputs, + unsigned n_outputs, + int max_width, + int max_height) +{ + unsigned i, j; + int x, y; + MetaConfiguration *ret; + MetaOutput *primary; + + ret = g_slice_new (MetaConfiguration); + make_config_key (ret, outputs, n_outputs, -1); + ret->outputs = g_new0 (MetaOutputConfig, n_outputs); + + /* Special case the simple case: one output, primary at preferred mode, + nothing else to do */ + if (n_outputs == 1) + { + ret->outputs[0].enabled = TRUE; + ret->outputs[0].rect.x = 0; + ret->outputs[0].rect.y = 0; + ret->outputs[0].rect.width = outputs[0].preferred_mode->width; + ret->outputs[0].rect.height = outputs[0].preferred_mode->height; + ret->outputs[0].refresh_rate = outputs[0].preferred_mode->refresh_rate; + ret->outputs[0].transform = WL_OUTPUT_TRANSFORM_NORMAL; + ret->outputs[0].is_primary = TRUE; + + return ret; + } + + /* If we reach this point, this is either the first time mutter runs + on this system ever, or we just hotplugged a new screen. + In the latter case, search for a configuration that includes one + less screen, then add the new one as a presentation screen + in preferred mode. + + XXX: but presentation mode is not implemented in the control-center + or in mutter core, so let's do extended for now. + */ + x = 0; + y = 0; + for (i = 0; i < n_outputs; i++) + { + MetaConfiguration key; + MetaConfiguration *ref; + + make_config_key (&key, outputs, n_outputs, i); + ref = g_hash_table_lookup (self->configs, &key); + config_clear (&key); + + if (ref) + { + for (j = 0; j < n_outputs; j++) + { + if (j < i) + { + g_assert (output_key_equal (&ret->keys[j], &ref->keys[j])); + ret->outputs[j] = ref->outputs[j]; + x = MAX (x, ref->outputs[j].rect.x + ref->outputs[j].rect.width); + y = MAX (y, ref->outputs[j].rect.y + ref->outputs[j].rect.height); + } + else if (j > i) + { + g_assert (output_key_equal (&ret->keys[j], &ref->keys[j - 1])); + ret->outputs[j] = ref->outputs[j - 1]; + x = MAX (x, ref->outputs[j - 1].rect.x + ref->outputs[j - 1].rect.width); + y = MAX (y, ref->outputs[j - 1].rect.y + ref->outputs[j - 1].rect.height); + } + else + { + ret->outputs[j].enabled = TRUE; + ret->outputs[j].rect.x = 0; + ret->outputs[j].rect.y = 0; + ret->outputs[j].rect.width = outputs[0].preferred_mode->width; + ret->outputs[j].rect.height = outputs[0].preferred_mode->height; + ret->outputs[j].refresh_rate = outputs[0].preferred_mode->refresh_rate; + ret->outputs[j].transform = WL_OUTPUT_TRANSFORM_NORMAL; + ret->outputs[j].is_primary = FALSE; + ret->outputs[j].is_presentation = FALSE; + } + } + + /* Place the new output at the right end of the screen, if it fits, + otherwise below it, otherwise disable it (or apply_configuration will fail) */ + if (x + ret->outputs[i].rect.width <= max_width) + ret->outputs[i].rect.x = x; + else if (y + ret->outputs[i].rect.height <= max_height) + ret->outputs[i].rect.y = y; + else + ret->outputs[i].enabled = FALSE; + + return ret; + } + } + + /* No previous configuration found, try with a really default one, which + is one primary that goes first and the rest to the right of it, extended. + */ + primary = find_primary_output (outputs, n_outputs); + + x = primary->preferred_mode->width; + for (i = 0; i < n_outputs; i++) + { + MetaOutput *output = &outputs[i]; + + ret->outputs[i].enabled = TRUE; + ret->outputs[i].rect.x = (output == primary) ? 0 : x; + ret->outputs[i].rect.y = 0; + ret->outputs[i].rect.width = output->preferred_mode->width; + ret->outputs[i].rect.height = output->preferred_mode->height; + ret->outputs[i].refresh_rate = output->preferred_mode->refresh_rate; + ret->outputs[i].transform = WL_OUTPUT_TRANSFORM_NORMAL; + ret->outputs[i].is_primary = (output == primary); + + /* Disable outputs that would go beyond framebuffer limits */ + if (ret->outputs[i].rect.x + ret->outputs[i].rect.width > max_width) + ret->outputs[i].enabled = FALSE; + else if (output != primary) + x += output->preferred_mode->width; + } + + return ret; +} + +static gboolean +ensure_at_least_one_output (MetaMonitorConfig *self, + MetaMonitorManager *manager, + MetaOutput *outputs, + unsigned n_outputs) +{ + MetaConfiguration *ret; + MetaOutput *primary; + unsigned i; + + /* Check that we have at least one active output */ + for (i = 0; i < n_outputs; i++) + if (outputs[i].crtc != NULL) + return TRUE; + + /* Oh no, we don't! Activate the primary one and disable everything else */ + + ret = g_slice_new (MetaConfiguration); + make_config_key (ret, outputs, n_outputs, -1); + ret->outputs = g_new0 (MetaOutputConfig, n_outputs); + + primary = find_primary_output (outputs, n_outputs); + + for (i = 0; i < n_outputs; i++) + { + MetaOutput *output = &outputs[i]; + + if (output == primary) + { + ret->outputs[i].enabled = TRUE; + ret->outputs[i].rect.x = 0; + ret->outputs[i].rect.y = 0; + ret->outputs[i].rect.width = output->preferred_mode->width; + ret->outputs[i].rect.height = output->preferred_mode->height; + ret->outputs[i].refresh_rate = output->preferred_mode->refresh_rate; + ret->outputs[i].transform = WL_OUTPUT_TRANSFORM_NORMAL; + ret->outputs[i].is_primary = TRUE; + } + else + { + ret->outputs[i].enabled = FALSE; + } + } + + apply_configuration (self, ret, manager, FALSE); + return FALSE; +} + +void +meta_monitor_config_make_default (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + MetaOutput *outputs; + MetaConfiguration *default_config; + unsigned n_outputs; + gboolean ok; + int max_width, max_height; + + outputs = meta_monitor_manager_get_outputs (manager, &n_outputs); + meta_monitor_manager_get_screen_limits (manager, &max_width, &max_height); + + default_config = make_default_config (self, outputs, n_outputs, max_width, max_height); + + if (default_config != NULL) + { + if (self->lid_is_closed && + default_config->n_outputs > 1 && + laptop_display_is_on (default_config)) + { + ok = apply_configuration (self, make_laptop_lid_config (default_config), + manager, FALSE); + config_free (default_config); + } + else + ok = apply_configuration (self, default_config, manager, FALSE); + } + else + ok = FALSE; + + if (!ok) + { + meta_warning ("Could not make default configuration for current output layout, leaving unconfigured\n"); + if (ensure_at_least_one_output (self, manager, outputs, n_outputs)) + meta_monitor_config_update_current (self, manager); + } +} + +static void +init_config_from_output (MetaOutputConfig *config, + MetaOutput *output) +{ + config->enabled = (output->crtc != NULL); + + if (!config->enabled) + return; + + config->rect = output->crtc->rect; + config->refresh_rate = output->crtc->current_mode->refresh_rate; + config->transform = output->crtc->transform; + config->is_primary = output->is_primary; + config->is_presentation = output->is_presentation; +} + +void +meta_monitor_config_update_current (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + MetaOutput *outputs; + unsigned n_outputs; + MetaConfiguration *current; + unsigned int i; + + outputs = meta_monitor_manager_get_outputs (manager, &n_outputs); + + current = g_slice_new (MetaConfiguration); + current->n_outputs = n_outputs; + current->outputs = g_new0 (MetaOutputConfig, n_outputs); + current->keys = g_new0 (MetaOutputKey, n_outputs); + + for (i = 0; i < current->n_outputs; i++) + { + init_key_from_output (¤t->keys[i], &outputs[i]); + init_config_from_output (¤t->outputs[i], &outputs[i]); + } + + if (self->current && !self->current_is_stored) + config_free (self->current); + + self->current = current; + self->current_is_stored = FALSE; +} + +void +meta_monitor_config_restore_previous (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + if (self->previous) + apply_configuration (self, self->previous, manager, FALSE); + else + { + if (!meta_monitor_config_apply_stored (self, manager)) + meta_monitor_config_make_default (self, manager); + } +} + +static void +turn_off_laptop_display (MetaMonitorConfig *self, + MetaMonitorManager *manager) +{ + MetaConfiguration *new; + + if (self->current->n_outputs == 1) + return; + + new = make_laptop_lid_config (self->current); + apply_configuration (self, new, manager, FALSE); +} + +static void +power_client_changed_cb (UpClient *client, + gpointer user_data) +{ + MetaMonitorManager *manager = meta_monitor_manager_get (); + MetaMonitorConfig *self = user_data; + gboolean is_closed; + + is_closed = up_client_get_lid_is_closed (self->up_client); + + if (is_closed != self->lid_is_closed) + { + self->lid_is_closed = is_closed; + + if (is_closed) + turn_off_laptop_display (self, manager); + else + meta_monitor_config_restore_previous (self, manager); + } +} + +typedef struct { + MetaMonitorConfig *config; + GString *buffer; +} SaveClosure; + +static void +saved_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SaveClosure *closure = user_data; + GError *error; + gboolean ok; + + error = NULL; + ok = g_file_replace_contents_finish (G_FILE (object), result, NULL, &error); + if (!ok) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + meta_warning ("Saving monitor configuration failed: %s\n", error->message); + + g_error_free (error); + } + + g_clear_object (&closure->config->save_cancellable); + g_object_unref (closure->config); + g_string_free (closure->buffer, TRUE); + + g_slice_free (SaveClosure, closure); +} + +static void +meta_monitor_config_save (MetaMonitorConfig *self) +{ + static const char * const rotation_map[4] = { + "normal", + "left", + "upside_down", + "right" + }; + SaveClosure *closure; + GString *buffer; + GHashTableIter iter; + MetaConfiguration *config; + unsigned int i; + + if (self->save_cancellable) + { + g_cancellable_cancel (self->save_cancellable); + g_object_unref (self->save_cancellable); + self->save_cancellable = NULL; + } + + self->save_cancellable = g_cancellable_new (); + + buffer = g_string_new ("\n"); + + g_hash_table_iter_init (&iter, self->configs); + while (g_hash_table_iter_next (&iter, (gpointer*) &config, NULL)) + { + /* Note: we don't distinguish clone vs non-clone here, that's + something for the UI (ie gnome-control-center) to handle, + and our configurations are more complex anyway. + */ + + g_string_append (buffer, + " \n" + " no\n"); + + for (i = 0; i < config->n_outputs; i++) + { + MetaOutputKey *key = &config->keys[i]; + MetaOutputConfig *output = &config->outputs[i]; + + g_string_append_printf (buffer, + " \n" + " %s\n" + " %s\n" + " %s\n", + key->connector, key->vendor, + key->product, key->serial); + + if (output->enabled) + { + char refresh_rate[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (refresh_rate, sizeof (refresh_rate), output->refresh_rate); + g_string_append_printf (buffer, + " %d\n" + " %d\n" + " %s\n" + " %d\n" + " %d\n" + " %s\n" + " %s\n" + " no\n" + " %s\n" + " %s\n", + output->rect.width, + output->rect.height, + refresh_rate, + output->rect.x, + output->rect.y, + rotation_map[output->transform & 0x3], + output->transform >= WL_OUTPUT_TRANSFORM_FLIPPED ? "yes" : "no", + output->is_primary ? "yes" : "no", + output->is_presentation ? "yes" : "no"); + } + + g_string_append (buffer, " \n"); + } + + g_string_append (buffer, " \n"); + } + + g_string_append (buffer, "\n"); + + closure = g_slice_new (SaveClosure); + closure->config = g_object_ref (self); + closure->buffer = buffer; + + g_file_replace_contents_async (self->file, + buffer->str, buffer->len, + NULL, /* etag */ + TRUE, + G_FILE_CREATE_REPLACE_DESTINATION, + self->save_cancellable, + saved_cb, closure); +} + +void +meta_monitor_config_make_persistent (MetaMonitorConfig *self) +{ + if (self->current_is_stored) + return; + + self->current_is_stored = TRUE; + g_hash_table_replace (self->configs, self->current, self->current); + + if (self->previous) + config_free (self->previous); + self->previous = NULL; + + meta_monitor_config_save (self); +} + +/* + * CRTC assignment + */ +typedef struct +{ + MetaConfiguration *config; + MetaMonitorManager *manager; + GHashTable *info; +} CrtcAssignment; + +static gboolean +output_can_clone (MetaOutput *output, + MetaOutput *clone) +{ + unsigned int i; + + for (i = 0; i < output->n_possible_clones; i++) + if (output->possible_clones[i] == clone) + return TRUE; + + return FALSE; +} + +static gboolean +can_clone (MetaCRTCInfo *info, + MetaOutput *output) +{ + unsigned int i; + + for (i = 0; i < info->outputs->len; ++i) + { + MetaOutput *clone = info->outputs->pdata[i]; + + if (!output_can_clone (clone, output)) + return FALSE; + } + + return TRUE; +} + +static gboolean +crtc_can_drive_output (MetaCRTC *crtc, + MetaOutput *output) +{ + unsigned int i; + + for (i = 0; i < output->n_possible_crtcs; i++) + if (output->possible_crtcs[i] == crtc) + return TRUE; + + return FALSE; +} + +static gboolean +output_supports_mode (MetaOutput *output, + MetaMonitorMode *mode) +{ + unsigned int i; + + for (i = 0; i < output->n_modes; i++) + if (output->modes[i] == mode) + return TRUE; + + return FALSE; +} + +static gboolean +crtc_assignment_assign (CrtcAssignment *assign, + MetaCRTC *crtc, + MetaMonitorMode *mode, + int x, + int y, + enum wl_output_transform transform, + MetaOutput *output) +{ + MetaCRTCInfo *info = g_hash_table_lookup (assign->info, crtc); + + if (!crtc_can_drive_output (crtc, output)) + return FALSE; + + if (!output_supports_mode (output, mode)) + return FALSE; + + if ((crtc->all_transforms & (1 << transform)) == 0) + return FALSE; + + if (info) + { + if (!(info->mode == mode && + info->x == x && + info->y == y && + info->transform == transform)) + return FALSE; + + if (!can_clone (info, output)) + return FALSE; + + g_ptr_array_add (info->outputs, output); + return TRUE; + } + else + { + MetaCRTCInfo *info = g_slice_new0 (MetaCRTCInfo); + + info->crtc = crtc; + info->mode = mode; + info->x = x; + info->y = y; + info->transform = transform; + info->outputs = g_ptr_array_new (); + + g_ptr_array_add (info->outputs, output); + g_hash_table_insert (assign->info, crtc, info); + + return TRUE; + } +} + +static void +crtc_assignment_unassign (CrtcAssignment *assign, + MetaCRTC *crtc, + MetaOutput *output) +{ + MetaCRTCInfo *info = g_hash_table_lookup (assign->info, crtc); + + if (info) + { + g_ptr_array_remove (info->outputs, output); + + if (info->outputs->len == 0) + g_hash_table_remove (assign->info, crtc); + } +} + +static MetaOutput * +find_output_by_key (MetaOutput *outputs, + unsigned int n_outputs, + MetaOutputKey *key) +{ + unsigned int i; + + for (i = 0; i < n_outputs; i++) + { + if (strcmp (outputs[i].name, key->connector) == 0) + { + /* This should be checked a lot earlier! */ + + g_warn_if_fail (strcmp (outputs[i].vendor, key->vendor) == 0 && + strcmp (outputs[i].product, key->product) == 0 && + strcmp (outputs[i].serial, key->serial) == 0); + return &outputs[i]; + } + } + + /* Just to satisfy GCC - this is a fatal error if occurs */ + return NULL; +} + +/* Check whether the given set of settings can be used + * at the same time -- ie. whether there is an assignment + * of CRTC's to outputs. + * + * Brute force - the number of objects involved is small + * enough that it doesn't matter. + */ +static gboolean +real_assign_crtcs (CrtcAssignment *assignment, + unsigned int output_num) +{ + MetaMonitorMode *modes; + MetaCRTC *crtcs; + MetaOutput *outputs; + unsigned int n_crtcs, n_modes, n_outputs; + MetaOutputKey *output_key; + MetaOutputConfig *output_config; + unsigned int i; + gboolean success; + + if (output_num == assignment->config->n_outputs) + return TRUE; + + output_key = &assignment->config->keys[output_num]; + output_config = &assignment->config->outputs[output_num]; + + /* It is always allowed for an output to be turned off */ + if (!output_config->enabled) + return real_assign_crtcs (assignment, output_num + 1); + + meta_monitor_manager_get_resources (assignment->manager, + &modes, &n_modes, + &crtcs, &n_crtcs, + &outputs, &n_outputs); + + success = FALSE; + + for (i = 0; i < n_crtcs; i++) + { + MetaCRTC *crtc = &crtcs[i]; + unsigned int pass; + + /* Make two passes, one where frequencies must match, then + * one where they don't have to + */ + for (pass = 0; pass < 2; pass++) + { + MetaOutput *output = find_output_by_key (outputs, n_outputs, output_key); + unsigned int j; + + for (j = 0; j < n_modes; j++) + { + MetaMonitorMode *mode = &modes[j]; + int width, height; + + if (meta_monitor_transform_is_rotated (output_config->transform)) + { + width = mode->height; + height = mode->width; + } + else + { + width = mode->width; + height = mode->height; + } + + if (width == output_config->rect.width && + height == output_config->rect.height && + (pass == 1 || mode->refresh_rate == output_config->refresh_rate)) + { + meta_verbose ("CRTC %ld: trying mode %dx%d@%fHz with output at %dx%d@%fHz (transform %d) (pass %d)\n", + crtc->crtc_id, + mode->width, mode->height, mode->refresh_rate, + output_config->rect.width, output_config->rect.height, output_config->refresh_rate, + output_config->transform, + pass); + + + if (crtc_assignment_assign (assignment, crtc, &modes[j], + output_config->rect.x, output_config->rect.y, + output_config->transform, + output)) + { + if (real_assign_crtcs (assignment, output_num + 1)) + { + success = TRUE; + goto out; + } + + crtc_assignment_unassign (assignment, crtc, output); + } + } + } + } + } + +out: + return success; +} + +static gboolean +meta_monitor_config_assign_crtcs (MetaConfiguration *config, + MetaMonitorManager *manager, + GPtrArray *crtcs, + GPtrArray *outputs) +{ + CrtcAssignment assignment; + GHashTableIter iter; + MetaCRTC *crtc; + MetaCRTCInfo *info; + unsigned int i; + MetaOutput *all_outputs; + unsigned int n_outputs; + + assignment.config = config; + assignment.manager = manager; + assignment.info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)meta_crtc_info_free); + + if (!real_assign_crtcs (&assignment, 0)) + { + meta_warning ("Could not assign CRTC to outputs, ignoring configuration\n"); + + g_hash_table_destroy (assignment.info); + return FALSE; + } + + g_hash_table_iter_init (&iter, assignment.info); + while (g_hash_table_iter_next (&iter, (void**)&crtc, (void**)&info)) + { + g_hash_table_iter_steal (&iter); + g_ptr_array_add (crtcs, info); + } + + all_outputs = meta_monitor_manager_get_outputs (manager, + &n_outputs); + g_assert (n_outputs == config->n_outputs); + + for (i = 0; i < n_outputs; i++) + { + MetaOutputInfo *output_info = g_slice_new (MetaOutputInfo); + MetaOutputConfig *output_config = &config->outputs[i]; + + output_info->output = find_output_by_key (all_outputs, n_outputs, + &config->keys[i]); + output_info->is_primary = output_config->is_primary; + output_info->is_presentation = output_config->is_presentation; + + g_ptr_array_add (outputs, output_info); + } + + g_hash_table_destroy (assignment.info); + return TRUE; +} + +void +meta_crtc_info_free (MetaCRTCInfo *info) +{ + g_ptr_array_free (info->outputs, TRUE); + g_slice_free (MetaCRTCInfo, info); +} + +void +meta_output_info_free (MetaOutputInfo *info) +{ + g_slice_free (MetaOutputInfo, info); +} diff --git a/src/core/monitor-private.h b/src/core/monitor-private.h new file mode 100644 index 000000000..b0fd70c4c --- /dev/null +++ b/src/core/monitor-private.h @@ -0,0 +1,390 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file screen-private.h Handling of monitor configuration + * + * Managing multiple monitors + * This file contains structures and functions that handle + * multiple monitors, including reading the current configuration + * and available hardware, and applying it. + * + * This interface is private to mutter, API users should look + * at MetaScreen instead. + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 Elijah Newren + * Copyright (C) 2013 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_MONITOR_PRIVATE_H +#define META_MONITOR_PRIVATE_H + +#include +#include + +#include "display-private.h" +#include +#include "stack-tracker.h" +#include "ui.h" +#ifdef HAVE_WAYLAND +#include +#endif +#include "meta-xrandr-shared.h" + +#include "meta-dbus-xrandr.h" + +typedef struct _MetaMonitorManagerClass MetaMonitorManagerClass; +typedef struct _MetaMonitorManager MetaMonitorManager; +typedef struct _MetaMonitorConfigClass MetaMonitorConfigClass; +typedef struct _MetaMonitorConfig MetaMonitorConfig; + +#ifndef HAVE_WAYLAND +enum wl_output_transform { + WL_OUTPUT_TRANSFORM_NORMAL, + WL_OUTPUT_TRANSFORM_90, + WL_OUTPUT_TRANSFORM_180, + WL_OUTPUT_TRANSFORM_270, + WL_OUTPUT_TRANSFORM_FLIPPED, + WL_OUTPUT_TRANSFORM_FLIPPED_90, + WL_OUTPUT_TRANSFORM_FLIPPED_180, + WL_OUTPUT_TRANSFORM_FLIPPED_270 +}; +#endif + +typedef struct _MetaOutput MetaOutput; +typedef struct _MetaCRTC MetaCRTC; +typedef struct _MetaMonitorMode MetaMonitorMode; +typedef struct _MetaMonitorInfo MetaMonitorInfo; +typedef struct _MetaCRTCInfo MetaCRTCInfo; +typedef struct _MetaOutputInfo MetaOutputInfo; + +struct _MetaOutput +{ + /* The CRTC driving this output, NULL if the output is not enabled */ + MetaCRTC *crtc; + /* The low-level ID of this output, used to apply back configuration */ + glong output_id; + char *name; + char *vendor; + char *product; + char *serial; + int width_mm; + int height_mm; + CoglSubpixelOrder subpixel_order; + + MetaMonitorMode *preferred_mode; + MetaMonitorMode **modes; + unsigned int n_modes; + + MetaCRTC **possible_crtcs; + unsigned int n_possible_crtcs; + + MetaOutput **possible_clones; + unsigned int n_possible_clones; + + int backlight; + int backlight_min; + int backlight_max; + + /* Used when changing configuration */ + gboolean is_dirty; + + /* The low-level bits used to build the high-level info + in MetaMonitorInfo + + XXX: flags maybe? + There is a lot of code that uses MonitorInfo->is_primary, + but nobody uses MetaOutput yet + */ + gboolean is_primary; + gboolean is_presentation; +}; + +struct _MetaCRTC +{ + glong crtc_id; + MetaRectangle rect; + MetaMonitorMode *current_mode; + enum wl_output_transform transform; + unsigned int all_transforms; + + /* Only used to build the logical configuration + from the HW one + */ + MetaMonitorInfo *logical_monitor; + + /* Used when changing configuration */ + gboolean is_dirty; +}; + +struct _MetaMonitorMode +{ + /* The low-level ID of this mode, used to apply back configuration */ + glong mode_id; + + int width; + int height; + float refresh_rate; +}; + +/** + * MetaMonitorInfo: + * + * A structure with high-level information about monitors. + * This corresponds to a subset of the compositor coordinate space. + * Clones are only reported once, irrespective of the way + * they're implemented (two CRTCs configured for the same + * coordinates or one CRTCs driving two outputs). Inactive CRTCs + * are ignored, and so are disabled outputs. + */ +struct _MetaMonitorInfo +{ + int number; + int xinerama_index; + MetaRectangle rect; + gboolean is_primary; + gboolean is_presentation; /* XXX: not yet used */ + gboolean in_fullscreen; + + /* The primary or first output for this monitor, 0 if we can't figure out. + It can be matched to an output_id of a MetaOutput. + + This is used as an opaque token on reconfiguration when switching from + clone to extened, to decide on what output the windows should go next + (it's an attempt to keep windows on the same monitor, and preferably on + the primary one). + */ + glong output_id; +}; + +/* + * MetaCRTCInfo: + * This represents the writable part of a CRTC, as deserialized from DBus + * or built by MetaMonitorConfig + * + * Note: differently from the other structures in this file, MetaCRTCInfo + * is handled by pointer. This is to accomodate the usage in MetaMonitorConfig + */ +struct _MetaCRTCInfo { + MetaCRTC *crtc; + MetaMonitorMode *mode; + int x; + int y; + enum wl_output_transform transform; + GPtrArray *outputs; +}; + +/* + * MetaOutputInfo: + * this is the same as MetaOutputInfo, but for CRTCs + */ +struct _MetaOutputInfo { + MetaOutput *output; + gboolean is_primary; + gboolean is_presentation; +}; + +#define META_TYPE_MONITOR_MANAGER (meta_monitor_manager_get_type ()) +#define META_MONITOR_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_MONITOR_MANAGER, MetaMonitorManager)) +#define META_MONITOR_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_MONITOR_MANAGER, MetaMonitorManagerClass)) +#define META_IS_MONITOR_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_MONITOR_MANAGER)) +#define META_IS_MONITOR_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_MONITOR_MANAGER)) +#define META_MONITOR_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_MONITOR_MANAGER, MetaMonitorManagerClass)) + +struct _MetaMonitorManager +{ + MetaDBusDisplayConfigSkeleton parent_instance; + + /* XXX: this structure is very badly + packed, but I like the logical organization + of fields */ + + gboolean in_init; + unsigned int serial; + + MetaPowerSave power_save_mode; + + int max_screen_width; + int max_screen_height; + int screen_width; + int screen_height; + + /* Outputs refer to physical screens, + CRTCs refer to stuff that can drive outputs + (like encoders, but less tied to the HW), + while monitor_infos refer to logical ones. + + See also the comment in monitor-private.h + */ + MetaOutput *outputs; + unsigned int n_outputs; + + MetaMonitorMode *modes; + unsigned int n_modes; + + MetaCRTC *crtcs; + unsigned int n_crtcs; + + MetaMonitorInfo *monitor_infos; + unsigned int n_monitor_infos; + int primary_monitor_index; + + int dbus_name_id; + + int persistent_timeout_id; + MetaMonitorConfig *config; + + GnomePnpIds *pnp_ids; +}; + +struct _MetaMonitorManagerClass +{ + MetaDBusDisplayConfigSkeletonClass parent_class; + + void (*read_current) (MetaMonitorManager *); + + char* (*get_edid_file) (MetaMonitorManager *, + MetaOutput *); + GBytes* (*read_edid) (MetaMonitorManager *, + MetaOutput *); + + void (*apply_configuration) (MetaMonitorManager *, + MetaCRTCInfo **, + unsigned int , + MetaOutputInfo **, + unsigned int); + + void (*set_power_save_mode) (MetaMonitorManager *, + MetaPowerSave); + + void (*change_backlight) (MetaMonitorManager *, + MetaOutput *, + int); + + void (*get_crtc_gamma) (MetaMonitorManager *, + MetaCRTC *, + gsize *, + unsigned short **, + unsigned short **, + unsigned short **); + void (*set_crtc_gamma) (MetaMonitorManager *, + MetaCRTC *, + gsize , + unsigned short *, + unsigned short *, + unsigned short *); + + gboolean (*handle_xevent) (MetaMonitorManager *, + XEvent *); +}; + +GType meta_monitor_manager_get_type (void); + +void meta_monitor_manager_initialize (void); +MetaMonitorManager *meta_monitor_manager_get (void); + +MetaMonitorInfo *meta_monitor_manager_get_monitor_infos (MetaMonitorManager *manager, + unsigned int *n_infos); + +MetaOutput *meta_monitor_manager_get_outputs (MetaMonitorManager *manager, + unsigned int *n_outputs); + +void meta_monitor_manager_get_resources (MetaMonitorManager *manager, + MetaMonitorMode **modes, + unsigned int *n_modes, + MetaCRTC **crtcs, + unsigned int *n_crtcs, + MetaOutput **outputs, + unsigned int *n_outputs); + +int meta_monitor_manager_get_primary_index (MetaMonitorManager *manager); + +gboolean meta_monitor_manager_handle_xevent (MetaMonitorManager *manager, + XEvent *event); + +void meta_monitor_manager_get_screen_size (MetaMonitorManager *manager, + int *width, + int *height); + +void meta_monitor_manager_get_screen_limits (MetaMonitorManager *manager, + int *width, + int *height); + +void meta_monitor_manager_apply_configuration (MetaMonitorManager *manager, + MetaCRTCInfo **crtcs, + unsigned int n_crtcs, + MetaOutputInfo **outputs, + unsigned int n_outputs); + +void meta_monitor_manager_confirm_configuration (MetaMonitorManager *manager, + gboolean ok); + +#define META_TYPE_MONITOR_MANAGER_XRANDR (meta_monitor_manager_xrandr_get_type ()) +#define META_MONITOR_MANAGER_XRANDR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_MONITOR_MANAGER_XRANDR, MetaMonitorManagerXrandr)) +#define META_MONITOR_MANAGER_XRANDR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_MONITOR_MANAGER_XRANDR, MetaMonitorManagerXrandrClass)) +#define META_IS_MONITOR_MANAGER_XRANDR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_MONITOR_MANAGER_XRANDR)) +#define META_IS_MONITOR_MANAGER_XRANDR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_MONITOR_MANAGER_XRANDR)) +#define META_MONITOR_MANAGER_XRANDR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_MONITOR_MANAGER_XRANDR, MetaMonitorManagerXrandrClass)) + +typedef struct _MetaMonitorManagerXrandrClass MetaMonitorManagerXrandrClass; +typedef struct _MetaMonitorManagerXrandr MetaMonitorManagerXrandr; + +GType meta_monitor_manager_xrandr_get_type (void); + +#define META_TYPE_MONITOR_CONFIG (meta_monitor_config_get_type ()) +#define META_MONITOR_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_MONITOR_CONFIG, MetaMonitorConfig)) +#define META_MONITOR_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_MONITOR_CONFIG, MetaMonitorConfigClass)) +#define META_IS_MONITOR_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_MONITOR_CONFIG)) +#define META_IS_MONITOR_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_MONITOR_CONFIG)) +#define META_MONITOR_CONFIG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_MONITOR_CONFIG, MetaMonitorConfigClass)) + +GType meta_monitor_config_get_type (void) G_GNUC_CONST; + +MetaMonitorConfig *meta_monitor_config_new (void); + +gboolean meta_monitor_config_match_current (MetaMonitorConfig *config, + MetaMonitorManager *manager); + +gboolean meta_monitor_config_apply_stored (MetaMonitorConfig *config, + MetaMonitorManager *manager); + +void meta_monitor_config_make_default (MetaMonitorConfig *config, + MetaMonitorManager *manager); + +void meta_monitor_config_update_current (MetaMonitorConfig *config, + MetaMonitorManager *manager); +void meta_monitor_config_make_persistent (MetaMonitorConfig *config); + +void meta_monitor_config_restore_previous (MetaMonitorConfig *config, + MetaMonitorManager *manager); + +void meta_crtc_info_free (MetaCRTCInfo *info); +void meta_output_info_free (MetaOutputInfo *info); + +/* Returns true if transform causes width and height to be inverted + This is true for the odd transforms in the enum */ +static inline gboolean +meta_monitor_transform_is_rotated (enum wl_output_transform transform) +{ + return (transform % 2); +} + +#endif diff --git a/src/core/monitor-xrandr.c b/src/core/monitor-xrandr.c new file mode 100644 index 000000000..bcceff80f --- /dev/null +++ b/src/core/monitor-xrandr.c @@ -0,0 +1,1039 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001, 2002 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat Inc. + * Some ICCCM manager selection code derived from fvwm2, + * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 Elijah Newren + * Copyright (C) 2013 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "monitor-private.h" + +#include "edid.h" + +#define ALL_WL_TRANSFORMS ((1 << (WL_OUTPUT_TRANSFORM_FLIPPED_270 + 1)) - 1) + +/* Look for DPI_FALLBACK in: + * http://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/xsettings/gsd-xsettings-manager.c + * for the reasoning */ +#define DPI_FALLBACK 96.0 + +struct _MetaMonitorManagerXrandr +{ + MetaMonitorManager parent_instance; + + Display *xdisplay; + XRRScreenResources *resources; + int time; + int rr_event_base; + int rr_error_base; +}; + +struct _MetaMonitorManagerXrandrClass +{ + MetaMonitorManagerClass parent_class; +}; + +G_DEFINE_TYPE (MetaMonitorManagerXrandr, meta_monitor_manager_xrandr, META_TYPE_MONITOR_MANAGER); + +static enum wl_output_transform +wl_transform_from_xrandr (Rotation rotation) +{ + static const enum wl_output_transform y_reflected_map[4] = { + WL_OUTPUT_TRANSFORM_FLIPPED_180, + WL_OUTPUT_TRANSFORM_FLIPPED_90, + WL_OUTPUT_TRANSFORM_FLIPPED, + WL_OUTPUT_TRANSFORM_FLIPPED_270 + }; + enum wl_output_transform ret; + + switch (rotation & 0x7F) + { + default: + case RR_Rotate_0: + ret = WL_OUTPUT_TRANSFORM_NORMAL; + break; + case RR_Rotate_90: + ret = WL_OUTPUT_TRANSFORM_90; + break; + case RR_Rotate_180: + ret = WL_OUTPUT_TRANSFORM_180; + break; + case RR_Rotate_270: + ret = WL_OUTPUT_TRANSFORM_270; + break; + } + + if (rotation & RR_Reflect_X) + return ret + 4; + else if (rotation & RR_Reflect_Y) + return y_reflected_map[ret]; + else + return ret; +} + +#define ALL_ROTATIONS (RR_Rotate_0 | RR_Rotate_90 | RR_Rotate_180 | RR_Rotate_270) + +static unsigned int +wl_transform_from_xrandr_all (Rotation rotation) +{ + unsigned ret; + + /* Handle the common cases first (none or all) */ + if (rotation == 0 || rotation == RR_Rotate_0) + return (1 << WL_OUTPUT_TRANSFORM_NORMAL); + + /* All rotations and one reflection -> all of them by composition */ + if ((rotation & ALL_ROTATIONS) && + ((rotation & RR_Reflect_X) || (rotation & RR_Reflect_Y))) + return ALL_WL_TRANSFORMS; + + ret = 1 << WL_OUTPUT_TRANSFORM_NORMAL; + if (rotation & RR_Rotate_90) + ret |= 1 << WL_OUTPUT_TRANSFORM_90; + if (rotation & RR_Rotate_180) + ret |= 1 << WL_OUTPUT_TRANSFORM_180; + if (rotation & RR_Rotate_270) + ret |= 1 << WL_OUTPUT_TRANSFORM_270; + if (rotation & (RR_Rotate_0 | RR_Reflect_X)) + ret |= 1 << WL_OUTPUT_TRANSFORM_FLIPPED; + if (rotation & (RR_Rotate_90 | RR_Reflect_X)) + ret |= 1 << WL_OUTPUT_TRANSFORM_FLIPPED_90; + if (rotation & (RR_Rotate_180 | RR_Reflect_X)) + ret |= 1 << WL_OUTPUT_TRANSFORM_FLIPPED_180; + if (rotation & (RR_Rotate_270 | RR_Reflect_X)) + ret |= 1 << WL_OUTPUT_TRANSFORM_FLIPPED_270; + + return ret; +} + +static gboolean +output_get_presentation_xrandr (MetaMonitorManagerXrandr *manager_xrandr, + MetaOutput *output) +{ + MetaDisplay *display = meta_get_display (); + gboolean value; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *buffer; + + XRRGetOutputProperty (manager_xrandr->xdisplay, + (XID)output->output_id, + display->atom__MUTTER_PRESENTATION_OUTPUT, + 0, G_MAXLONG, False, False, XA_CARDINAL, + &actual_type, &actual_format, + &nitems, &bytes_after, &buffer); + + if (actual_type != XA_CARDINAL || actual_format != 32 || + nitems < 1) + return FALSE; + + value = ((int*)buffer)[0]; + + XFree (buffer); + return value; +} + +static int +normalize_backlight (MetaOutput *output, + int hw_value) +{ + return round ((double)(hw_value - output->backlight_min) / + (output->backlight_max - output->backlight_min) * 100.0); +} + +static int +output_get_backlight_xrandr (MetaMonitorManagerXrandr *manager_xrandr, + MetaOutput *output) +{ + MetaDisplay *display = meta_get_display (); + gboolean value; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *buffer; + + XRRGetOutputProperty (manager_xrandr->xdisplay, + (XID)output->output_id, + display->atom_BACKLIGHT, + 0, G_MAXLONG, False, False, XA_INTEGER, + &actual_type, &actual_format, + &nitems, &bytes_after, &buffer); + + if (actual_type != XA_INTEGER || actual_format != 32 || + nitems < 1) + return -1; + + value = ((int*)buffer)[0]; + + XFree (buffer); + return normalize_backlight (output, value); +} + +static void +output_get_backlight_limits_xrandr (MetaMonitorManagerXrandr *manager_xrandr, + MetaOutput *output) +{ + MetaDisplay *display = meta_get_display (); + XRRPropertyInfo *info; + + meta_error_trap_push (display); + info = XRRQueryOutputProperty (manager_xrandr->xdisplay, + (XID)output->output_id, + display->atom_BACKLIGHT); + meta_error_trap_pop (display); + + if (info == NULL) + { + meta_verbose ("could not get output property for %s\n", output->name); + return; + } + + if (!info->range || info->num_values != 2) + { + meta_verbose ("backlight %s was not range\n", output->name); + goto out; + } + + output->backlight_min = info->values[0]; + output->backlight_max = info->values[1]; + +out: + XFree (info); +} + +static int +compare_outputs (const void *one, + const void *two) +{ + const MetaOutput *o_one = one, *o_two = two; + + return strcmp (o_one->name, o_two->name); +} + +static guint8 * +get_edid_property (Display *dpy, + RROutput output, + Atom atom, + gsize *len) +{ + unsigned char *prop; + int actual_format; + unsigned long nitems, bytes_after; + Atom actual_type; + guint8 *result; + + XRRGetOutputProperty (dpy, output, atom, + 0, 100, False, False, + AnyPropertyType, + &actual_type, &actual_format, + &nitems, &bytes_after, &prop); + + if (actual_type == XA_INTEGER && actual_format == 8) + { + result = g_memdup (prop, nitems); + if (len) + *len = nitems; + } + else + { + result = NULL; + } + + XFree (prop); + + return result; +} + +static GBytes * +read_output_edid (MetaMonitorManagerXrandr *manager_xrandr, + XID output_id) +{ + Atom edid_atom; + guint8 *result; + gsize len; + + edid_atom = XInternAtom (manager_xrandr->xdisplay, "EDID", FALSE); + result = get_edid_property (manager_xrandr->xdisplay, output_id, edid_atom, &len); + + if (!result) + { + edid_atom = XInternAtom (manager_xrandr->xdisplay, "EDID_DATA", FALSE); + result = get_edid_property (manager_xrandr->xdisplay, output_id, edid_atom, &len); + } + + if (!result) + { + edid_atom = XInternAtom (manager_xrandr->xdisplay, "XFree86_DDC_EDID1_RAWDATA", FALSE); + result = get_edid_property (manager_xrandr->xdisplay, output_id, edid_atom, &len); + } + + if (result) + { + if (len > 0 && len % 128 == 0) + return g_bytes_new_take (result, len); + else + g_free (result); + } + + return NULL; +} + +static void +meta_monitor_manager_xrandr_read_current (MetaMonitorManager *manager) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + XRRScreenResources *resources; + RROutput primary_output; + unsigned int i, j, k; + unsigned int n_actual_outputs; + int min_width, min_height; + Screen *screen; + BOOL dpms_capable, dpms_enabled; + CARD16 dpms_state; + + if (manager_xrandr->resources) + XRRFreeScreenResources (manager_xrandr->resources); + manager_xrandr->resources = NULL; + + meta_error_trap_push (meta_get_display ()); + dpms_capable = DPMSCapable (manager_xrandr->xdisplay); + meta_error_trap_pop (meta_get_display ()); + + if (dpms_capable && + DPMSInfo (manager_xrandr->xdisplay, &dpms_state, &dpms_enabled) && + dpms_enabled) + { + switch (dpms_state) + { + case DPMSModeOn: + manager->power_save_mode = META_POWER_SAVE_ON; + break; + case DPMSModeStandby: + manager->power_save_mode = META_POWER_SAVE_STANDBY; + break; + case DPMSModeSuspend: + manager->power_save_mode = META_POWER_SAVE_SUSPEND; + break; + case DPMSModeOff: + manager->power_save_mode = META_POWER_SAVE_OFF; + break; + default: + manager->power_save_mode = META_POWER_SAVE_UNKNOWN; + break; + } + } + else + { + manager->power_save_mode = META_POWER_SAVE_UNKNOWN; + } + + XRRGetScreenSizeRange (manager_xrandr->xdisplay, DefaultRootWindow (manager_xrandr->xdisplay), + &min_width, + &min_height, + &manager->max_screen_width, + &manager->max_screen_height); + + screen = ScreenOfDisplay (manager_xrandr->xdisplay, + DefaultScreen (manager_xrandr->xdisplay)); + /* This is updated because we called RRUpdateConfiguration below */ + manager->screen_width = WidthOfScreen (screen); + manager->screen_height = HeightOfScreen (screen); + + resources = XRRGetScreenResourcesCurrent (manager_xrandr->xdisplay, + DefaultRootWindow (manager_xrandr->xdisplay)); + if (!resources) + return; + + manager_xrandr->resources = resources; + manager_xrandr->time = resources->configTimestamp; + manager->n_outputs = resources->noutput; + manager->n_crtcs = resources->ncrtc; + manager->n_modes = resources->nmode; + manager->outputs = g_new0 (MetaOutput, manager->n_outputs); + manager->modes = g_new0 (MetaMonitorMode, manager->n_modes); + manager->crtcs = g_new0 (MetaCRTC, manager->n_crtcs); + + for (i = 0; i < (unsigned)resources->nmode; i++) + { + XRRModeInfo *xmode = &resources->modes[i]; + MetaMonitorMode *mode; + + mode = &manager->modes[i]; + + mode->mode_id = xmode->id; + mode->width = xmode->width; + mode->height = xmode->height; + mode->refresh_rate = (xmode->dotClock / + ((float)xmode->hTotal * xmode->vTotal)); + } + + for (i = 0; i < (unsigned)resources->ncrtc; i++) + { + XRRCrtcInfo *crtc; + MetaCRTC *meta_crtc; + + crtc = XRRGetCrtcInfo (manager_xrandr->xdisplay, resources, resources->crtcs[i]); + + meta_crtc = &manager->crtcs[i]; + + meta_crtc->crtc_id = resources->crtcs[i]; + meta_crtc->rect.x = crtc->x; + meta_crtc->rect.y = crtc->y; + meta_crtc->rect.width = crtc->width; + meta_crtc->rect.height = crtc->height; + meta_crtc->is_dirty = FALSE; + meta_crtc->transform = wl_transform_from_xrandr (crtc->rotation); + meta_crtc->all_transforms = wl_transform_from_xrandr_all (crtc->rotations); + + for (j = 0; j < (unsigned)resources->nmode; j++) + { + if (resources->modes[j].id == crtc->mode) + { + meta_crtc->current_mode = &manager->modes[j]; + break; + } + } + + XRRFreeCrtcInfo (crtc); + } + + primary_output = XRRGetOutputPrimary (manager_xrandr->xdisplay, + DefaultRootWindow (manager_xrandr->xdisplay)); + + n_actual_outputs = 0; + for (i = 0; i < (unsigned)resources->noutput; i++) + { + XRROutputInfo *output; + MetaOutput *meta_output; + + output = XRRGetOutputInfo (manager_xrandr->xdisplay, resources, resources->outputs[i]); + + meta_output = &manager->outputs[n_actual_outputs]; + + if (output->connection != RR_Disconnected) + { + GBytes *edid; + MonitorInfo *parsed_edid; + + meta_output->output_id = resources->outputs[i]; + meta_output->name = g_strdup (output->name); + + edid = read_output_edid (manager_xrandr, meta_output->output_id); + if (edid) + { + gsize len; + + parsed_edid = decode_edid (g_bytes_get_data (edid, &len)); + if (parsed_edid) + { + meta_output->vendor = g_strndup (parsed_edid->manufacturer_code, 4); + if (parsed_edid->dsc_product_name[0]) + meta_output->product = g_strndup (parsed_edid->dsc_product_name, 14); + else + meta_output->product = g_strdup_printf ("0x%04x", (unsigned)parsed_edid->product_code); + if (parsed_edid->dsc_serial_number[0]) + meta_output->serial = g_strndup (parsed_edid->dsc_serial_number, 14); + else + meta_output->serial = g_strdup_printf ("0x%08x", parsed_edid->serial_number); + + g_free (parsed_edid); + } + + g_bytes_unref (edid); + } + + if (!meta_output->vendor) + { + meta_output->vendor = g_strdup ("unknown"); + meta_output->product = g_strdup ("unknown"); + meta_output->serial = g_strdup ("unknown"); + } + meta_output->width_mm = output->mm_width; + meta_output->height_mm = output->mm_height; + meta_output->subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN; + + meta_output->n_modes = output->nmode; + meta_output->modes = g_new0 (MetaMonitorMode *, meta_output->n_modes); + for (j = 0; j < meta_output->n_modes; j++) + { + for (k = 0; k < manager->n_modes; k++) + { + if (output->modes[j] == (XID)manager->modes[k].mode_id) + { + meta_output->modes[j] = &manager->modes[k]; + break; + } + } + } + meta_output->preferred_mode = meta_output->modes[0]; + + meta_output->n_possible_crtcs = output->ncrtc; + meta_output->possible_crtcs = g_new0 (MetaCRTC *, meta_output->n_possible_crtcs); + for (j = 0; j < (unsigned)output->ncrtc; j++) + { + for (k = 0; k < manager->n_crtcs; k++) + { + if ((XID)manager->crtcs[k].crtc_id == output->crtcs[j]) + { + meta_output->possible_crtcs[j] = &manager->crtcs[k]; + break; + } + } + } + + meta_output->crtc = NULL; + for (j = 0; j < manager->n_crtcs; j++) + { + if ((XID)manager->crtcs[j].crtc_id == output->crtc) + { + meta_output->crtc = &manager->crtcs[j]; + break; + } + } + + meta_output->n_possible_clones = output->nclone; + meta_output->possible_clones = g_new0 (MetaOutput *, meta_output->n_possible_clones); + /* We can build the list of clones now, because we don't have the list of outputs + yet, so temporarily set the pointers to the bare XIDs, and then we'll fix them + in a second pass + */ + for (j = 0; j < (unsigned)output->nclone; j++) + { + meta_output->possible_clones[j] = GINT_TO_POINTER (output->clones[j]); + } + + meta_output->is_primary = ((XID)meta_output->output_id == primary_output); + meta_output->is_presentation = output_get_presentation_xrandr (manager_xrandr, meta_output); + output_get_backlight_limits_xrandr (manager_xrandr, meta_output); + + if (!(meta_output->backlight_min == 0 && meta_output->backlight_max == 0)) + meta_output->backlight = output_get_backlight_xrandr (manager_xrandr, meta_output); + else + meta_output->backlight = -1; + + n_actual_outputs++; + } + + XRRFreeOutputInfo (output); + } + + manager->n_outputs = n_actual_outputs; + + /* Sort the outputs for easier handling in MetaMonitorConfig */ + qsort (manager->outputs, manager->n_outputs, sizeof (MetaOutput), compare_outputs); + + /* Now fix the clones */ + for (i = 0; i < manager->n_outputs; i++) + { + MetaOutput *meta_output; + + meta_output = &manager->outputs[i]; + + for (j = 0; j < meta_output->n_possible_clones; j++) + { + RROutput clone = GPOINTER_TO_INT (meta_output->possible_clones[j]); + + for (k = 0; k < manager->n_outputs; k++) + { + if (clone == (XID)manager->outputs[k].output_id) + { + meta_output->possible_clones[j] = &manager->outputs[k]; + break; + } + } + } + } +} + +static GBytes * +meta_monitor_manager_xrandr_read_edid (MetaMonitorManager *manager, + MetaOutput *output) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + + return read_output_edid (manager_xrandr, output->output_id); +} + +static void +meta_monitor_manager_xrandr_set_power_save_mode (MetaMonitorManager *manager, + MetaPowerSave mode) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + CARD16 state; + + switch (mode) { + case META_POWER_SAVE_ON: + state = DPMSModeOn; + break; + case META_POWER_SAVE_STANDBY: + state = DPMSModeStandby; + break; + case META_POWER_SAVE_SUSPEND: + state = DPMSModeSuspend; + break; + case META_POWER_SAVE_OFF: + state = DPMSModeOff; + break; + default: + return; + } + + meta_error_trap_push (meta_get_display ()); + DPMSForceLevel (manager_xrandr->xdisplay, state); + DPMSSetTimeouts (manager_xrandr->xdisplay, 0, 0, 0); + meta_error_trap_pop (meta_get_display ()); +} + +static Rotation +wl_transform_to_xrandr (enum wl_output_transform transform) +{ + switch (transform) + { + case WL_OUTPUT_TRANSFORM_NORMAL: + return RR_Rotate_0; + case WL_OUTPUT_TRANSFORM_90: + return RR_Rotate_90; + case WL_OUTPUT_TRANSFORM_180: + return RR_Rotate_180; + case WL_OUTPUT_TRANSFORM_270: + return RR_Rotate_270; + case WL_OUTPUT_TRANSFORM_FLIPPED: + return RR_Reflect_X | RR_Rotate_0; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + return RR_Reflect_X | RR_Rotate_90; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return RR_Reflect_X | RR_Rotate_180; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return RR_Reflect_X | RR_Rotate_270; + } + + g_assert_not_reached (); +} + +static void +output_set_presentation_xrandr (MetaMonitorManagerXrandr *manager_xrandr, + MetaOutput *output, + gboolean presentation) +{ + MetaDisplay *display = meta_get_display (); + int value = presentation; + + XRRChangeOutputProperty (manager_xrandr->xdisplay, + (XID)output->output_id, + display->atom__MUTTER_PRESENTATION_OUTPUT, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*) &value, 1); +} + +static void +meta_monitor_manager_xrandr_apply_configuration (MetaMonitorManager *manager, + MetaCRTCInfo **crtcs, + unsigned int n_crtcs, + MetaOutputInfo **outputs, + unsigned int n_outputs) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + unsigned i; + int width, height, width_mm, height_mm; + + meta_display_grab (meta_get_display ()); + + /* First compute the new size of the screen (framebuffer) */ + width = 0; height = 0; + for (i = 0; i < n_crtcs; i++) + { + MetaCRTCInfo *crtc_info = crtcs[i]; + MetaCRTC *crtc = crtc_info->crtc; + crtc->is_dirty = TRUE; + + if (crtc_info->mode == NULL) + continue; + + if (meta_monitor_transform_is_rotated (crtc_info->transform)) + { + width = MAX (width, crtc_info->x + crtc_info->mode->height); + height = MAX (height, crtc_info->y + crtc_info->mode->width); + } + else + { + width = MAX (width, crtc_info->x + crtc_info->mode->width); + height = MAX (height, crtc_info->y + crtc_info->mode->height); + } + } + + /* Second disable all newly disabled CRTCs, or CRTCs that in the previous + configuration would be outside the new framebuffer (otherwise X complains + loudly when resizing) + CRTC will be enabled again after resizing the FB + */ + for (i = 0; i < n_crtcs; i++) + { + MetaCRTCInfo *crtc_info = crtcs[i]; + MetaCRTC *crtc = crtc_info->crtc; + + if (crtc_info->mode == NULL || + crtc->rect.x + crtc->rect.width > width || + crtc->rect.y + crtc->rect.height > height) + { + XRRSetCrtcConfig (manager_xrandr->xdisplay, + manager_xrandr->resources, + (XID)crtc->crtc_id, + manager_xrandr->time, + 0, 0, + None, + RR_Rotate_0, + NULL, 0); + + crtc->rect.x = 0; + crtc->rect.y = 0; + crtc->rect.width = 0; + crtc->rect.height = 0; + crtc->current_mode = NULL; + } + } + + /* Disable CRTCs not mentioned in the list */ + for (i = 0; i < manager->n_crtcs; i++) + { + MetaCRTC *crtc = &manager->crtcs[i]; + + if (crtc->is_dirty) + { + crtc->is_dirty = FALSE; + continue; + } + if (crtc->current_mode == NULL) + continue; + + XRRSetCrtcConfig (manager_xrandr->xdisplay, + manager_xrandr->resources, + (XID)crtc->crtc_id, + manager_xrandr->time, + 0, 0, + None, + RR_Rotate_0, + NULL, 0); + + crtc->rect.x = 0; + crtc->rect.y = 0; + crtc->rect.width = 0; + crtc->rect.height = 0; + crtc->current_mode = NULL; + } + + g_assert (width > 0 && height > 0); + /* The 'physical size' of an X screen is meaningless if that screen + * can consist of many monitors. So just pick a size that make the + * dpi 96. + * + * Firefox and Evince apparently believe what X tells them. + */ + width_mm = (width / DPI_FALLBACK) * 25.4 + 0.5; + height_mm = (height / DPI_FALLBACK) * 25.4 + 0.5; + meta_error_trap_push (meta_get_display ()); + XRRSetScreenSize (manager_xrandr->xdisplay, DefaultRootWindow (manager_xrandr->xdisplay), + width, height, width_mm, height_mm); + meta_error_trap_pop (meta_get_display ()); + + for (i = 0; i < n_crtcs; i++) + { + MetaCRTCInfo *crtc_info = crtcs[i]; + MetaCRTC *crtc = crtc_info->crtc; + + if (crtc_info->mode != NULL) + { + MetaMonitorMode *mode; + XID *outputs; + unsigned int j, n_outputs; + int width, height; + Status ok; + unsigned long old_controlled_mask; + unsigned long new_controlled_mask; + + mode = crtc_info->mode; + + n_outputs = crtc_info->outputs->len; + outputs = g_new (XID, n_outputs); + + old_controlled_mask = 0; + for (j = 0; j < manager->n_outputs; j++) + { + MetaOutput *output; + + output = &manager->outputs[j]; + + if (output->crtc == crtc) + old_controlled_mask |= 1UL << j; + } + + new_controlled_mask = 0; + for (j = 0; j < n_outputs; j++) + { + MetaOutput *output; + + output = ((MetaOutput**)crtc_info->outputs->pdata)[j]; + + output->is_dirty = TRUE; + output->crtc = crtc; + new_controlled_mask |= 1UL << j; + + outputs[j] = output->output_id; + } + + if (crtc->current_mode == mode && + crtc->rect.x == crtc_info->x && + crtc->rect.y == crtc_info->y && + crtc->transform == crtc_info->transform && + old_controlled_mask == new_controlled_mask) + { + /* No change */ + goto next; + } + + meta_error_trap_push (meta_get_display ()); + ok = XRRSetCrtcConfig (manager_xrandr->xdisplay, + manager_xrandr->resources, + (XID)crtc->crtc_id, + manager_xrandr->time, + crtc_info->x, crtc_info->y, + (XID)mode->mode_id, + wl_transform_to_xrandr (crtc_info->transform), + outputs, n_outputs); + meta_error_trap_pop (meta_get_display ()); + + if (ok != Success) + { + meta_warning ("Configuring CRTC %d with mode %d (%d x %d @ %f) at position %d, %d and transfrom %u failed\n", + (unsigned)(crtc->crtc_id), (unsigned)(mode->mode_id), + mode->width, mode->height, (float)mode->refresh_rate, + crtc_info->x, crtc_info->y, crtc_info->transform); + goto next; + } + + if (meta_monitor_transform_is_rotated (crtc_info->transform)) + { + width = mode->height; + height = mode->width; + } + else + { + width = mode->width; + height = mode->height; + } + + crtc->rect.x = crtc_info->x; + crtc->rect.y = crtc_info->y; + crtc->rect.width = width; + crtc->rect.height = height; + crtc->current_mode = mode; + crtc->transform = crtc_info->transform; + + next: + g_free (outputs); + } + } + + for (i = 0; i < n_outputs; i++) + { + MetaOutputInfo *output_info = outputs[i]; + MetaOutput *output = output_info->output; + + if (output_info->is_primary) + { + XRRSetOutputPrimary (manager_xrandr->xdisplay, + DefaultRootWindow (manager_xrandr->xdisplay), + (XID)output_info->output->output_id); + } + + output_set_presentation_xrandr (manager_xrandr, + output_info->output, + output_info->is_presentation); + + output->is_primary = output_info->is_primary; + output->is_presentation = output_info->is_presentation; + } + + /* Disable outputs not mentioned in the list */ + for (i = 0; i < manager->n_outputs; i++) + { + MetaOutput *output = &manager->outputs[i]; + + if (output->is_dirty) + { + output->is_dirty = FALSE; + continue; + } + + output->crtc = NULL; + output->is_primary = FALSE; + } + + meta_display_ungrab (meta_get_display ()); +} + +static void +meta_monitor_manager_xrandr_change_backlight (MetaMonitorManager *manager, + MetaOutput *output, + gint value) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + MetaDisplay *display = meta_get_display (); + int hw_value; + + hw_value = round ((double)value / 100.0 * output->backlight_max + output->backlight_min); + + meta_error_trap_push (display); + XRRChangeOutputProperty (manager_xrandr->xdisplay, + (XID)output->output_id, + display->atom_BACKLIGHT, + XA_INTEGER, 32, PropModeReplace, + (unsigned char *) &hw_value, 1); + meta_error_trap_pop (display); + + /* We're not selecting for property notifies, so update the value immediately */ + output->backlight = normalize_backlight (output, hw_value); +} + +static void +meta_monitor_manager_xrandr_get_crtc_gamma (MetaMonitorManager *manager, + MetaCRTC *crtc, + gsize *size, + unsigned short **red, + unsigned short **green, + unsigned short **blue) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + XRRCrtcGamma *gamma; + + gamma = XRRGetCrtcGamma (manager_xrandr->xdisplay, (XID)crtc->crtc_id); + + *size = gamma->size; + *red = g_memdup (gamma->red, sizeof (unsigned short) * gamma->size); + *green = g_memdup (gamma->green, sizeof (unsigned short) * gamma->size); + *blue = g_memdup (gamma->blue, sizeof (unsigned short) * gamma->size); + + XRRFreeGamma (gamma); +} + +static void +meta_monitor_manager_xrandr_set_crtc_gamma (MetaMonitorManager *manager, + MetaCRTC *crtc, + gsize size, + unsigned short *red, + unsigned short *green, + unsigned short *blue) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + XRRCrtcGamma *gamma; + + gamma = XRRAllocGamma (size); + memcpy (gamma->red, red, sizeof (unsigned short) * size); + memcpy (gamma->green, green, sizeof (unsigned short) * size); + memcpy (gamma->blue, blue, sizeof (unsigned short) * size); + + XRRSetCrtcGamma (manager_xrandr->xdisplay, (XID)crtc->crtc_id, gamma); + + XRRFreeGamma (gamma); +} + +static gboolean +meta_monitor_manager_xrandr_handle_xevent (MetaMonitorManager *manager, + XEvent *event) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (manager); + + if ((event->type - manager_xrandr->rr_event_base) != RRScreenChangeNotify) + return FALSE; + + XRRUpdateConfiguration (event); + return TRUE; +} + +static void +meta_monitor_manager_xrandr_init (MetaMonitorManagerXrandr *manager_xrandr) +{ + MetaDisplay *display = meta_get_display (); + + manager_xrandr->xdisplay = display->xdisplay; + + if (!XRRQueryExtension (manager_xrandr->xdisplay, + &manager_xrandr->rr_event_base, + &manager_xrandr->rr_error_base)) + { + return; + } + else + { + /* We only use ScreenChangeNotify, but GDK uses the others, + and we don't want to step on its toes */ + XRRSelectInput (manager_xrandr->xdisplay, + DefaultRootWindow (manager_xrandr->xdisplay), + RRScreenChangeNotifyMask + | RRCrtcChangeNotifyMask + | RROutputPropertyNotifyMask); + } +} + +static void +meta_monitor_manager_xrandr_finalize (GObject *object) +{ + MetaMonitorManagerXrandr *manager_xrandr = META_MONITOR_MANAGER_XRANDR (object); + + if (manager_xrandr->resources) + XRRFreeScreenResources (manager_xrandr->resources); + manager_xrandr->resources = NULL; + + G_OBJECT_CLASS (meta_monitor_manager_xrandr_parent_class)->finalize (object); +} + +static void +meta_monitor_manager_xrandr_class_init (MetaMonitorManagerXrandrClass *klass) +{ + MetaMonitorManagerClass *manager_class = META_MONITOR_MANAGER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meta_monitor_manager_xrandr_finalize; + + manager_class->read_current = meta_monitor_manager_xrandr_read_current; + manager_class->read_edid = meta_monitor_manager_xrandr_read_edid; + manager_class->apply_configuration = meta_monitor_manager_xrandr_apply_configuration; + manager_class->set_power_save_mode = meta_monitor_manager_xrandr_set_power_save_mode; + manager_class->change_backlight = meta_monitor_manager_xrandr_change_backlight; + manager_class->get_crtc_gamma = meta_monitor_manager_xrandr_get_crtc_gamma; + manager_class->set_crtc_gamma = meta_monitor_manager_xrandr_set_crtc_gamma; + manager_class->handle_xevent = meta_monitor_manager_xrandr_handle_xevent; +} + diff --git a/src/core/monitor.c b/src/core/monitor.c new file mode 100644 index 000000000..c6b46496c --- /dev/null +++ b/src/core/monitor.c @@ -0,0 +1,1548 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001, 2002 Havoc Pennington + * Copyright (C) 2002, 2003 Red Hat Inc. + * Some ICCCM manager selection code derived from fvwm2, + * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team + * Copyright (C) 2003 Rob Adams + * Copyright (C) 2004-2006 Elijah Newren + * Copyright (C) 2013 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include "monitor-private.h" + +#include "meta-dbus-xrandr.h" + +#define ALL_WL_TRANSFORMS ((1 << (WL_OUTPUT_TRANSFORM_FLIPPED_270 + 1)) - 1) + +enum { + CONFIRM_DISPLAY_CHANGE, + SIGNALS_LAST +}; + +enum { + PROP_0, + PROP_POWER_SAVE_MODE, + PROP_LAST +}; + +static int signals[SIGNALS_LAST]; + +static void meta_monitor_manager_display_config_init (MetaDBusDisplayConfigIface *iface); + +G_DEFINE_TYPE_WITH_CODE (MetaMonitorManager, meta_monitor_manager, META_DBUS_TYPE_DISPLAY_CONFIG_SKELETON, + G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_DISPLAY_CONFIG, meta_monitor_manager_display_config_init)); + +static void free_output_array (MetaOutput *old_outputs, + int n_old_outputs); +static void invalidate_logical_config (MetaMonitorManager *manager); +static void initialize_dbus_interface (MetaMonitorManager *manager); + +static void +read_current_dummy (MetaMonitorManager *manager) +{ + /* The dummy monitor config has: + - one enabled output, LVDS, primary, at 0x0 and 1024x768 + - one free CRTC + - two disabled outputs + - three modes, 1024x768, 800x600 and 640x480 + - no clones are possible (use different CRTCs) + + Low-level IDs should be assigned sequentially, to + mimick what XRandR and KMS do + */ + + manager->max_screen_width = 65535; + manager->max_screen_height = 65535; + manager->screen_width = 1024; + manager->screen_height = 768; + + manager->modes = g_new0 (MetaMonitorMode, 6); + manager->n_modes = 6; + + manager->modes[0].mode_id = 1; + manager->modes[0].width = 1024; + manager->modes[0].height = 768; + manager->modes[0].refresh_rate = 60.0; + + manager->modes[1].mode_id = 2; + manager->modes[1].width = 800; + manager->modes[1].height = 600; + manager->modes[1].refresh_rate = 60.0; + + manager->modes[2].mode_id = 3; + manager->modes[2].width = 640; + manager->modes[2].height = 480; + manager->modes[2].refresh_rate = 60.0; + + manager->modes[3].mode_id = 4; + manager->modes[3].width = 1920; + manager->modes[3].height = 1080; + manager->modes[3].refresh_rate = 60.0; + + manager->modes[4].mode_id = 5; + manager->modes[4].width = 1920; + manager->modes[4].height = 1080; + manager->modes[4].refresh_rate = 55.0; + + manager->modes[5].mode_id = 6; + manager->modes[5].width = 1600; + manager->modes[5].height = 900; + manager->modes[5].refresh_rate = 60.0; + + manager->crtcs = g_new0 (MetaCRTC, 3); + manager->n_crtcs = 3; + + manager->crtcs[0].crtc_id = 4; + manager->crtcs[0].rect.x = 0; + manager->crtcs[0].rect.y = 0; + manager->crtcs[0].rect.width = manager->modes[0].width; + manager->crtcs[0].rect.height = manager->modes[0].height; + manager->crtcs[0].current_mode = &manager->modes[0]; + manager->crtcs[0].transform = WL_OUTPUT_TRANSFORM_NORMAL; + manager->crtcs[0].all_transforms = ALL_WL_TRANSFORMS; + manager->crtcs[0].is_dirty = FALSE; + manager->crtcs[0].logical_monitor = NULL; + + manager->crtcs[1].crtc_id = 5; + manager->crtcs[1].rect.x = 0; + manager->crtcs[1].rect.y = 0; + manager->crtcs[1].rect.width = 0; + manager->crtcs[1].rect.height = 0; + manager->crtcs[1].current_mode = NULL; + manager->crtcs[1].transform = WL_OUTPUT_TRANSFORM_NORMAL; + manager->crtcs[1].all_transforms = ALL_WL_TRANSFORMS; + manager->crtcs[1].is_dirty = FALSE; + manager->crtcs[1].logical_monitor = NULL; + + manager->crtcs[2].crtc_id = 5; + manager->crtcs[2].rect.x = 0; + manager->crtcs[2].rect.y = 0; + manager->crtcs[2].rect.width = 0; + manager->crtcs[2].rect.height = 0; + manager->crtcs[2].current_mode = NULL; + manager->crtcs[2].transform = WL_OUTPUT_TRANSFORM_NORMAL; + manager->crtcs[2].all_transforms = ALL_WL_TRANSFORMS; + manager->crtcs[2].is_dirty = FALSE; + manager->crtcs[2].logical_monitor = NULL; + + manager->outputs = g_new0 (MetaOutput, 3); + manager->n_outputs = 3; + + manager->outputs[0].crtc = NULL; + manager->outputs[0].output_id = 6; + manager->outputs[0].name = g_strdup ("HDMI"); + manager->outputs[0].vendor = g_strdup ("MetaProducts Inc."); + manager->outputs[0].product = g_strdup ("unknown"); + manager->outputs[0].serial = g_strdup ("0xC0F01A"); + manager->outputs[0].width_mm = 510; + manager->outputs[0].height_mm = 287; + manager->outputs[0].subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN; + manager->outputs[0].preferred_mode = &manager->modes[3]; + manager->outputs[0].n_modes = 5; + manager->outputs[0].modes = g_new0 (MetaMonitorMode *, 5); + manager->outputs[0].modes[0] = &manager->modes[0]; + manager->outputs[0].modes[1] = &manager->modes[1]; + manager->outputs[0].modes[2] = &manager->modes[2]; + manager->outputs[0].modes[3] = &manager->modes[3]; + manager->outputs[0].modes[4] = &manager->modes[4]; + manager->outputs[0].n_possible_crtcs = 3; + manager->outputs[0].possible_crtcs = g_new0 (MetaCRTC *, 3); + manager->outputs[0].possible_crtcs[0] = &manager->crtcs[0]; + manager->outputs[0].possible_crtcs[1] = &manager->crtcs[1]; + manager->outputs[0].possible_crtcs[2] = &manager->crtcs[2]; + manager->outputs[0].n_possible_clones = 0; + manager->outputs[0].possible_clones = g_new0 (MetaOutput *, 0); + manager->outputs[0].backlight = -1; + manager->outputs[0].backlight_min = 0; + manager->outputs[0].backlight_max = 0; + + manager->outputs[1].crtc = &manager->crtcs[0]; + manager->outputs[1].output_id = 7; + manager->outputs[1].name = g_strdup ("LVDS"); + manager->outputs[1].vendor = g_strdup ("MetaProducts Inc."); + manager->outputs[1].product = g_strdup ("unknown"); + manager->outputs[1].serial = g_strdup ("0xC0FFEE"); + manager->outputs[1].width_mm = 222; + manager->outputs[1].height_mm = 125; + manager->outputs[1].subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN; + manager->outputs[1].preferred_mode = &manager->modes[5]; + manager->outputs[1].n_modes = 4; + manager->outputs[1].modes = g_new0 (MetaMonitorMode *, 4); + manager->outputs[1].modes[0] = &manager->modes[0]; + manager->outputs[1].modes[1] = &manager->modes[1]; + manager->outputs[1].modes[2] = &manager->modes[2]; + manager->outputs[1].modes[3] = &manager->modes[5]; + manager->outputs[1].n_possible_crtcs = 3; + manager->outputs[1].possible_crtcs = g_new0 (MetaCRTC *, 3); + manager->outputs[1].possible_crtcs[0] = &manager->crtcs[0]; + manager->outputs[1].possible_crtcs[1] = &manager->crtcs[1]; + manager->outputs[1].possible_crtcs[2] = &manager->crtcs[2]; + manager->outputs[1].n_possible_clones = 0; + manager->outputs[1].possible_clones = g_new0 (MetaOutput *, 0); + manager->outputs[1].backlight = -1; + manager->outputs[1].backlight_min = 0; + manager->outputs[1].backlight_max = 0; + + manager->outputs[2].crtc = NULL; + manager->outputs[2].output_id = 8; + manager->outputs[2].name = g_strdup ("VGA"); + manager->outputs[2].vendor = g_strdup ("MetaProducts Inc."); + manager->outputs[2].product = g_strdup ("unknown"); + manager->outputs[2].serial = g_strdup ("0xC4FE"); + manager->outputs[2].width_mm = 309; + manager->outputs[2].height_mm = 174; + manager->outputs[2].subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN; + manager->outputs[2].preferred_mode = &manager->modes[0]; + manager->outputs[2].n_modes = 3; + manager->outputs[2].modes = g_new0 (MetaMonitorMode *, 3); + manager->outputs[2].modes[0] = &manager->modes[0]; + manager->outputs[2].modes[1] = &manager->modes[1]; + manager->outputs[2].modes[2] = &manager->modes[2]; + manager->outputs[2].n_possible_crtcs = 3; + manager->outputs[2].possible_crtcs = g_new0 (MetaCRTC *, 3); + manager->outputs[2].possible_crtcs[0] = &manager->crtcs[0]; + manager->outputs[2].possible_crtcs[1] = &manager->crtcs[1]; + manager->outputs[2].possible_crtcs[2] = &manager->crtcs[2]; + manager->outputs[2].n_possible_clones = 0; + manager->outputs[2].possible_clones = g_new0 (MetaOutput *, 0); + manager->outputs[2].backlight = -1; + manager->outputs[2].backlight_min = 0; + manager->outputs[2].backlight_max = 0; +} + +static void +apply_config_dummy (MetaMonitorManager *manager, + MetaCRTCInfo **crtcs, + unsigned int n_crtcs, + MetaOutputInfo **outputs, + unsigned int n_outputs) +{ + unsigned i; + int screen_width = 0, screen_height = 0; + + for (i = 0; i < n_crtcs; i++) + { + MetaCRTCInfo *crtc_info = crtcs[i]; + MetaCRTC *crtc = crtc_info->crtc; + crtc->is_dirty = TRUE; + + if (crtc_info->mode == NULL) + { + crtc->rect.x = 0; + crtc->rect.y = 0; + crtc->rect.width = 0; + crtc->rect.height = 0; + crtc->current_mode = NULL; + } + else + { + MetaMonitorMode *mode; + MetaOutput *output; + int i, n_outputs; + int width, height; + + mode = crtc_info->mode; + + if (meta_monitor_transform_is_rotated (crtc_info->transform)) + { + width = mode->height; + height = mode->width; + } + else + { + width = mode->width; + height = mode->height; + } + + crtc->rect.x = crtc_info->x; + crtc->rect.y = crtc_info->y; + crtc->rect.width = width; + crtc->rect.height = height; + crtc->current_mode = mode; + crtc->transform = crtc_info->transform; + + screen_width = MAX (screen_width, crtc_info->x + width); + screen_height = MAX (screen_height, crtc_info->y + height); + + n_outputs = crtc_info->outputs->len; + for (i = 0; i < n_outputs; i++) + { + output = ((MetaOutput**)crtc_info->outputs->pdata)[i]; + + output->is_dirty = TRUE; + output->crtc = crtc; + } + } + } + + for (i = 0; i < n_outputs; i++) + { + MetaOutputInfo *output_info = outputs[i]; + MetaOutput *output = output_info->output; + + output->is_primary = output_info->is_primary; + output->is_presentation = output_info->is_presentation; + } + + /* Disable CRTCs not mentioned in the list */ + for (i = 0; i < manager->n_crtcs; i++) + { + MetaCRTC *crtc = &manager->crtcs[i]; + + crtc->logical_monitor = NULL; + + if (crtc->is_dirty) + { + crtc->is_dirty = FALSE; + continue; + } + + crtc->rect.x = 0; + crtc->rect.y = 0; + crtc->rect.width = 0; + crtc->rect.height = 0; + crtc->current_mode = NULL; + } + + /* Disable outputs not mentioned in the list */ + for (i = 0; i < manager->n_outputs; i++) + { + MetaOutput *output = &manager->outputs[i]; + + if (output->is_dirty) + { + output->is_dirty = FALSE; + continue; + } + + output->crtc = NULL; + output->is_primary = FALSE; + } + + manager->screen_width = screen_width; + manager->screen_height = screen_height; + + invalidate_logical_config (manager); +} + +static GBytes * +read_edid_dummy (MetaMonitorManager *manager, + MetaOutput *output) +{ + return NULL; +} + +static char * +get_edid_file_dummy (MetaMonitorManager *manager, + MetaOutput *output) +{ + return NULL; +} + +static void +meta_monitor_manager_init (MetaMonitorManager *manager) +{ +} + +static void +read_current_config (MetaMonitorManager *manager) +{ + manager->serial++; + + META_MONITOR_MANAGER_GET_CLASS (manager)->read_current (manager); +} + +/* + * make_logical_config: + * + * Turn outputs and CRTCs into logical MetaMonitorInfo, + * that will be used by the core and API layer (MetaScreen + * and friends) + */ +static void +make_logical_config (MetaMonitorManager *manager) +{ + GArray *monitor_infos; + unsigned int i, j; + + monitor_infos = g_array_sized_new (FALSE, TRUE, sizeof (MetaMonitorInfo), + manager->n_outputs); + + /* Walk the list of MetaCRTCs, and build a MetaMonitorInfo + for each of them, unless they reference a rectangle that + is already there. + */ + for (i = 0; i < manager->n_crtcs; i++) + { + MetaCRTC *crtc = &manager->crtcs[i]; + + /* Ignore CRTCs not in use */ + if (crtc->current_mode == NULL) + continue; + + for (j = 0; j < monitor_infos->len; j++) + { + MetaMonitorInfo *info = &g_array_index (monitor_infos, MetaMonitorInfo, i); + if (meta_rectangle_equal (&crtc->rect, + &info->rect)) + { + crtc->logical_monitor = info; + break; + } + } + + if (crtc->logical_monitor == NULL) + { + MetaMonitorInfo info; + + info.number = monitor_infos->len; + info.rect = crtc->rect; + info.is_primary = FALSE; + /* This starts true because we want + is_presentation only if all outputs are + marked as such (while for primary it's enough + that any is marked) + */ + info.is_presentation = TRUE; + info.in_fullscreen = -1; + info.output_id = 0; + + g_array_append_val (monitor_infos, info); + + crtc->logical_monitor = &g_array_index (monitor_infos, MetaMonitorInfo, + info.number); + } + } + + /* Now walk the list of outputs applying extended properties (primary + and presentation) + */ + for (i = 0; i < manager->n_outputs; i++) + { + MetaOutput *output; + MetaMonitorInfo *info; + + output = &manager->outputs[i]; + + /* Ignore outputs that are not active */ + if (output->crtc == NULL) + continue; + + /* We must have a logical monitor on every CRTC at this point */ + g_assert (output->crtc->logical_monitor != NULL); + + info = output->crtc->logical_monitor; + + info->is_primary = info->is_primary || output->is_primary; + info->is_presentation = info->is_presentation && output->is_presentation; + + if (output->is_primary || info->output_id == 0) + info->output_id = output->output_id; + + if (info->is_primary) + manager->primary_monitor_index = info->number; + } + + manager->n_monitor_infos = monitor_infos->len; + manager->monitor_infos = (void*)g_array_free (monitor_infos, FALSE); +} + +static MetaMonitorManager * +meta_monitor_manager_new (void) +{ + const char *env; + GType type; + + env = g_getenv ("META_DEBUG_MULTIMONITOR"); + + if (env == NULL) + type = META_TYPE_MONITOR_MANAGER_XRANDR; + else if (strcmp (env, "xrandr") == 0) + type = META_TYPE_MONITOR_MANAGER_XRANDR; + else + type = META_TYPE_MONITOR_MANAGER; + + return g_object_new (type, NULL); +} + +static void +meta_monitor_manager_constructed (GObject *object) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (object); + + manager->in_init = TRUE; + + manager->config = meta_monitor_config_new (); + + read_current_config (manager); + + if (!meta_monitor_config_apply_stored (manager->config, manager)) + meta_monitor_config_make_default (manager->config, manager); + + /* Under XRandR, we don't rebuild our data structures until we see + the RRScreenNotify event, but at least at startup we want to have + the right configuration immediately. + + The other backends keep the data structures always updated, + so this is not needed. + */ + if (META_IS_MONITOR_MANAGER_XRANDR (manager)) + { + MetaOutput *old_outputs; + MetaCRTC *old_crtcs; + MetaMonitorMode *old_modes; + int n_old_outputs; + + old_outputs = manager->outputs; + n_old_outputs = manager->n_outputs; + old_modes = manager->modes; + old_crtcs = manager->crtcs; + + read_current_config (manager); + + free_output_array (old_outputs, n_old_outputs); + g_free (old_modes); + g_free (old_crtcs); + } + + make_logical_config (manager); + initialize_dbus_interface (manager); + + manager->in_init = FALSE; +} + +static void +meta_monitor_manager_set_power_save_mode (MetaMonitorManager *manager, + MetaPowerSave mode) +{ + MetaMonitorManagerClass *klass; + + if (mode == manager->power_save_mode) + return; + + if (manager->power_save_mode == META_POWER_SAVE_UNKNOWN || + mode == META_POWER_SAVE_UNKNOWN) + return; + + klass = META_MONITOR_MANAGER_GET_CLASS (manager); + if (klass->set_power_save_mode) + klass->set_power_save_mode (manager, mode); + + manager->power_save_mode = mode; +} + +static void +free_output_array (MetaOutput *old_outputs, + int n_old_outputs) +{ + int i; + + for (i = 0; i < n_old_outputs; i++) + { + g_free (old_outputs[i].name); + g_free (old_outputs[i].vendor); + g_free (old_outputs[i].product); + g_free (old_outputs[i].serial); + g_free (old_outputs[i].modes); + g_free (old_outputs[i].possible_crtcs); + g_free (old_outputs[i].possible_clones); + } + + g_free (old_outputs); +} + +static void +meta_monitor_manager_finalize (GObject *object) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (object); + + free_output_array (manager->outputs, manager->n_outputs); + g_free (manager->monitor_infos); + g_free (manager->modes); + g_free (manager->crtcs); + + G_OBJECT_CLASS (meta_monitor_manager_parent_class)->finalize (object); +} + +static void +meta_monitor_manager_dispose (GObject *object) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (object); + + if (manager->dbus_name_id != 0) + { + g_bus_unown_name (manager->dbus_name_id); + manager->dbus_name_id = 0; + } + + G_OBJECT_CLASS (meta_monitor_manager_parent_class)->dispose (object); +} + +static void +meta_monitor_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaMonitorManager *self = META_MONITOR_MANAGER (object); + + switch (prop_id) + { + case PROP_POWER_SAVE_MODE: + meta_monitor_manager_set_power_save_mode (self, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_monitor_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MetaMonitorManager *self = META_MONITOR_MANAGER (object); + + switch (prop_id) + { + case PROP_POWER_SAVE_MODE: + g_value_set_int (value, self->power_save_mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_monitor_manager_class_init (MetaMonitorManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = meta_monitor_manager_constructed; + object_class->get_property = meta_monitor_manager_get_property; + object_class->set_property = meta_monitor_manager_set_property; + object_class->dispose = meta_monitor_manager_dispose; + object_class->finalize = meta_monitor_manager_finalize; + + klass->read_current = read_current_dummy; + klass->apply_configuration = apply_config_dummy; + klass->get_edid_file = get_edid_file_dummy; + klass->read_edid = read_edid_dummy; + + signals[CONFIRM_DISPLAY_CHANGE] = + g_signal_new ("confirm-display-change", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + g_object_class_override_property (object_class, PROP_POWER_SAVE_MODE, "power-save-mode"); +} + +static const double known_diagonals[] = { + 12.1, + 13.3, + 15.6 +}; + +static char * +diagonal_to_str (double d) +{ + unsigned 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_name (MetaMonitorManager *manager, + MetaOutput *output) +{ + if (g_str_has_prefix (output->name, "LVDS") || + g_str_has_prefix (output->name, "eDP")) + return g_strdup (_("Built-in display")); + + if (output->width_mm != -1 && output->height_mm != -1) + { + double d = sqrt (output->width_mm * output->width_mm + + output->height_mm * output->height_mm); + char *inches = diagonal_to_str (d / 25.4); + char *vendor_name; + char *ret; + + if (g_strcmp0 (output->vendor, "unknown") != 0) + { + if (!manager->pnp_ids) + manager->pnp_ids = gnome_pnp_ids_new (); + + vendor_name = gnome_pnp_ids_get_pnp_id (manager->pnp_ids, + output->vendor); + + ret = g_strdup_printf ("%s %s", vendor_name, inches); + + g_free (vendor_name); + } + else + { + /* TRANSLATORS: this is a monitor name (in case we don't know + the vendor), it's Unknown followed by a size in inches, + like 'Unknown 15"' + */ + ret = g_strdup_printf (_("Unknown %s"), inches); + } + + g_free (inches); + return ret; + } + else + { + return g_strdup (output->vendor); + } +} + +static gboolean +meta_monitor_manager_handle_get_resources (MetaDBusDisplayConfig *skeleton, + GDBusMethodInvocation *invocation) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton); + MetaMonitorManagerClass *manager_class = META_MONITOR_MANAGER_GET_CLASS (skeleton); + GVariantBuilder crtc_builder, output_builder, mode_builder; + unsigned int i, j; + + g_variant_builder_init (&crtc_builder, G_VARIANT_TYPE ("a(uxiiiiiuaua{sv})")); + g_variant_builder_init (&output_builder, G_VARIANT_TYPE ("a(uxiausauaua{sv})")); + g_variant_builder_init (&mode_builder, G_VARIANT_TYPE ("a(uxuud)")); + + for (i = 0; i < manager->n_crtcs; i++) + { + MetaCRTC *crtc = &manager->crtcs[i]; + GVariantBuilder transforms; + + g_variant_builder_init (&transforms, G_VARIANT_TYPE ("au")); + for (j = 0; j <= WL_OUTPUT_TRANSFORM_FLIPPED_270; j++) + if (crtc->all_transforms & (1 << j)) + g_variant_builder_add (&transforms, "u", j); + + g_variant_builder_add (&crtc_builder, "(uxiiiiiuaua{sv})", + i, /* ID */ + crtc->crtc_id, + (int)crtc->rect.x, + (int)crtc->rect.y, + (int)crtc->rect.width, + (int)crtc->rect.height, + (int)(crtc->current_mode ? crtc->current_mode - manager->modes : -1), + crtc->transform, + &transforms, + NULL /* properties */); + } + + for (i = 0; i < manager->n_outputs; i++) + { + MetaOutput *output = &manager->outputs[i]; + GVariantBuilder crtcs, modes, clones, properties; + GBytes *edid; + char *edid_file; + + g_variant_builder_init (&crtcs, G_VARIANT_TYPE ("au")); + for (j = 0; j < output->n_possible_crtcs; j++) + g_variant_builder_add (&crtcs, "u", + (unsigned)(output->possible_crtcs[j] - manager->crtcs)); + + g_variant_builder_init (&modes, G_VARIANT_TYPE ("au")); + for (j = 0; j < output->n_modes; j++) + g_variant_builder_add (&modes, "u", + (unsigned)(output->modes[j] - manager->modes)); + + g_variant_builder_init (&clones, G_VARIANT_TYPE ("au")); + for (j = 0; j < output->n_possible_clones; j++) + g_variant_builder_add (&clones, "u", + (unsigned)(output->possible_clones[j] - manager->outputs)); + + g_variant_builder_init (&properties, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&properties, "{sv}", "vendor", + g_variant_new_string (output->vendor)); + g_variant_builder_add (&properties, "{sv}", "product", + g_variant_new_string (output->product)); + g_variant_builder_add (&properties, "{sv}", "serial", + g_variant_new_string (output->serial)); + g_variant_builder_add (&properties, "{sv}", "width-mm", + g_variant_new_int32 (output->width_mm)); + g_variant_builder_add (&properties, "{sv}", "height-mm", + g_variant_new_int32 (output->height_mm)); + g_variant_builder_add (&properties, "{sv}", "display-name", + g_variant_new_take_string (make_display_name (manager, output))); + g_variant_builder_add (&properties, "{sv}", "backlight", + g_variant_new_int32 (output->backlight)); + g_variant_builder_add (&properties, "{sv}", "primary", + g_variant_new_boolean (output->is_primary)); + g_variant_builder_add (&properties, "{sv}", "presentation", + g_variant_new_boolean (output->is_presentation)); + + edid_file = manager_class->get_edid_file (manager, output); + if (edid_file) + { + g_variant_builder_add (&properties, "{sv}", "edid-file", + g_variant_new_take_string (edid_file)); + } + else + { + edid = manager_class->read_edid (manager, output); + + if (edid) + { + g_variant_builder_add (&properties, "{sv}", "edid", + g_variant_new_from_bytes (G_VARIANT_TYPE ("ay"), + edid, TRUE)); + g_bytes_unref (edid); + } + } + + g_variant_builder_add (&output_builder, "(uxiausauaua{sv})", + i, /* ID */ + output->output_id, + (int)(output->crtc ? output->crtc - manager->crtcs : -1), + &crtcs, + output->name, + &modes, + &clones, + &properties); + } + + for (i = 0; i < manager->n_modes; i++) + { + MetaMonitorMode *mode = &manager->modes[i]; + + g_variant_builder_add (&mode_builder, "(uxuud)", + i, /* ID */ + mode->mode_id, + mode->width, + mode->height, + (double)mode->refresh_rate); + } + + meta_dbus_display_config_complete_get_resources (skeleton, + invocation, + manager->serial, + g_variant_builder_end (&crtc_builder), + g_variant_builder_end (&output_builder), + g_variant_builder_end (&mode_builder), + manager->max_screen_width, + manager->max_screen_height); + return TRUE; +} + +static gboolean +output_can_config (MetaOutput *output, + MetaCRTC *crtc, + MetaMonitorMode *mode) +{ + unsigned int i; + gboolean ok = FALSE; + + for (i = 0; i < output->n_possible_crtcs && !ok; i++) + ok = output->possible_crtcs[i] == crtc; + + if (!ok) + return FALSE; + + if (mode == NULL) + return TRUE; + + ok = FALSE; + for (i = 0; i < output->n_modes && !ok; i++) + ok = output->modes[i] == mode; + + return ok; +} + +static gboolean +output_can_clone (MetaOutput *output, + MetaOutput *clone) +{ + unsigned int i; + gboolean ok = FALSE; + + for (i = 0; i < output->n_possible_clones && !ok; i++) + ok = output->possible_clones[i] == clone; + + return ok; +} + +void +meta_monitor_manager_apply_configuration (MetaMonitorManager *manager, + MetaCRTCInfo **crtcs, + unsigned int n_crtcs, + MetaOutputInfo **outputs, + unsigned int n_outputs) +{ + META_MONITOR_MANAGER_GET_CLASS (manager)->apply_configuration (manager, + crtcs, n_crtcs, + outputs, n_outputs); +} + +static gboolean +save_config_timeout (gpointer user_data) +{ + MetaMonitorManager *manager = user_data; + + meta_monitor_config_restore_previous (manager->config, manager); + + manager->persistent_timeout_id = 0; + return G_SOURCE_REMOVE; +} + +static gboolean +meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleton, + GDBusMethodInvocation *invocation, + guint serial, + gboolean persistent, + GVariant *crtcs, + GVariant *outputs) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton); + GVariantIter crtc_iter, output_iter, *nested_outputs; + GVariant *properties; + guint crtc_id; + int new_mode, x, y; + int new_screen_width, new_screen_height; + guint transform; + guint output_id; + GPtrArray *crtc_infos, *output_infos; + + if (serial != manager->serial) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "The requested configuration is based on stale information"); + return TRUE; + } + + crtc_infos = g_ptr_array_new_full (g_variant_n_children (crtcs), + (GDestroyNotify) meta_crtc_info_free); + output_infos = g_ptr_array_new_full (g_variant_n_children (outputs), + (GDestroyNotify) meta_output_info_free); + + /* Validate all arguments */ + new_screen_width = 0; new_screen_height = 0; + g_variant_iter_init (&crtc_iter, crtcs); + while (g_variant_iter_loop (&crtc_iter, "(uiiiuaua{sv})", + &crtc_id, &new_mode, &x, &y, &transform, + &nested_outputs, NULL)) + { + MetaCRTCInfo *crtc_info; + MetaOutput *first_output; + MetaCRTC *crtc; + MetaMonitorMode *mode; + guint output_id; + + crtc_info = g_slice_new (MetaCRTCInfo); + crtc_info->outputs = g_ptr_array_new (); + + if (crtc_id >= manager->n_crtcs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid CRTC id"); + return TRUE; + } + crtc = &manager->crtcs[crtc_id]; + crtc_info->crtc = crtc; + + if (new_mode != -1 && (new_mode < 0 || (unsigned)new_mode >= manager->n_modes)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid mode id"); + return TRUE; + } + mode = new_mode != -1 ? &manager->modes[new_mode] : NULL; + crtc_info->mode = mode; + + if (mode) + { + int width, height; + + if (meta_monitor_transform_is_rotated (transform)) + { + width = mode->height; + height = mode->width; + } + else + { + width = mode->width; + height = mode->height; + } + + if (x < 0 || + x + width > manager->max_screen_width || + y < 0 || + y + height > manager->max_screen_height) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid CRTC geometry"); + return TRUE; + } + + new_screen_width = MAX (new_screen_width, x + width); + new_screen_height = MAX (new_screen_height, y + height); + crtc_info->x = x; + crtc_info->y = y; + } + else + { + crtc_info->x = 0; + crtc_info->y = 0; + } + + if (transform < WL_OUTPUT_TRANSFORM_NORMAL || + transform > WL_OUTPUT_TRANSFORM_FLIPPED_270 || + ((crtc->all_transforms & (1 << transform)) == 0)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid transform"); + return TRUE; + } + crtc_info->transform = transform; + + first_output = NULL; + while (g_variant_iter_loop (nested_outputs, "u", &output_id)) + { + MetaOutput *output; + + if (output_id >= manager->n_outputs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid output id"); + return TRUE; + } + output = &manager->outputs[output_id]; + + if (!output_can_config (output, crtc, mode)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Output cannot be assigned to this CRTC or mode"); + return TRUE; + } + g_ptr_array_add (crtc_info->outputs, output); + + if (first_output) + { + if (!output_can_clone (output, first_output)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Outputs cannot be cloned"); + return TRUE; + } + } + else + first_output = output; + } + + if (!first_output && mode) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Mode specified without outputs?"); + return TRUE; + } + + g_ptr_array_add (crtc_infos, crtc_info); + } + + if (new_screen_width == 0 || new_screen_height == 0) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Refusing to disable all outputs"); + return TRUE; + } + + g_variant_iter_init (&output_iter, outputs); + while (g_variant_iter_loop (&output_iter, "(u@a{sv})", &output_id, &properties)) + { + MetaOutputInfo *output_info; + gboolean primary, presentation; + + if (output_id >= manager->n_outputs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid output id"); + return TRUE; + } + + output_info = g_slice_new0 (MetaOutputInfo); + output_info->output = &manager->outputs[output_id]; + + if (g_variant_lookup (properties, "primary", "b", &primary)) + output_info->is_primary = primary; + + if (g_variant_lookup (properties, "presentation", "b", &presentation)) + output_info->is_presentation = presentation; + + g_ptr_array_add (output_infos, output_info); + } + + /* If we were in progress of making a persistent change and we see a + new request, it's likely that the old one failed in some way, so + don't save it, but also don't queue for restoring it. + */ + if (manager->persistent_timeout_id && persistent) + { + g_source_remove (manager->persistent_timeout_id); + manager->persistent_timeout_id = 0; + } + + meta_monitor_manager_apply_configuration (manager, + (MetaCRTCInfo**)crtc_infos->pdata, + crtc_infos->len, + (MetaOutputInfo**)output_infos->pdata, + output_infos->len); + + g_ptr_array_unref (crtc_infos); + g_ptr_array_unref (output_infos); + + /* Update MetaMonitorConfig data structures immediately so that we + don't revert the change at the next XRandR event, then ask the plugin + manager (through MetaScreen) to confirm the display change with the + appropriate UI. Then wait 20 seconds and if not confirmed, revert the + configuration. + */ + meta_monitor_config_update_current (manager->config, manager); + if (persistent) + { + manager->persistent_timeout_id = g_timeout_add_seconds (20, save_config_timeout, manager); + g_signal_emit (manager, signals[CONFIRM_DISPLAY_CHANGE], 0); + } + + meta_dbus_display_config_complete_apply_configuration (skeleton, invocation); + return TRUE; +} + +void +meta_monitor_manager_confirm_configuration (MetaMonitorManager *manager, + gboolean ok) +{ + if (!manager->persistent_timeout_id) + { + /* too late */ + return; + } + + g_source_remove (manager->persistent_timeout_id); + manager->persistent_timeout_id = 0; + + if (ok) + meta_monitor_config_make_persistent (manager->config); + else + meta_monitor_config_restore_previous (manager->config, manager); +} + +static gboolean +meta_monitor_manager_handle_change_backlight (MetaDBusDisplayConfig *skeleton, + GDBusMethodInvocation *invocation, + guint serial, + guint output_id, + gint value) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton); + MetaOutput *output; + + if (serial != manager->serial) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "The requested configuration is based on stale information"); + return TRUE; + } + + if (output_id >= manager->n_outputs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid output id"); + return TRUE; + } + output = &manager->outputs[output_id]; + + if (value < 0 || value > 100) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid backlight value"); + return TRUE; + } + + if (output->backlight == -1 || + (output->backlight_min == 0 && output->backlight_max == 0)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Output does not support changing backlight"); + return TRUE; + } + + META_MONITOR_MANAGER_GET_CLASS (manager)->change_backlight (manager, output, value); + + meta_dbus_display_config_complete_change_backlight (skeleton, invocation, output->backlight); + return TRUE; +} + +static gboolean +meta_monitor_manager_handle_get_crtc_gamma (MetaDBusDisplayConfig *skeleton, + GDBusMethodInvocation *invocation, + guint serial, + guint crtc_id) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton); + MetaMonitorManagerClass *klass; + MetaCRTC *crtc; + gsize size; + unsigned short *red; + unsigned short *green; + unsigned short *blue; + GBytes *red_bytes, *green_bytes, *blue_bytes; + GVariant *red_v, *green_v, *blue_v; + + if (serial != manager->serial) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "The requested configuration is based on stale information"); + return TRUE; + } + + if (crtc_id >= manager->n_crtcs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid crtc id"); + return TRUE; + } + crtc = &manager->crtcs[crtc_id]; + + klass = META_MONITOR_MANAGER_GET_CLASS (manager); + if (klass->get_crtc_gamma) + klass->get_crtc_gamma (manager, crtc, &size, &red, &green, &blue); + else + { + size = 0; + red = green = blue = NULL; + } + + red_bytes = g_bytes_new_take (red, size * sizeof (unsigned short)); + green_bytes = g_bytes_new_take (green, size * sizeof (unsigned short)); + blue_bytes = g_bytes_new_take (blue, size * sizeof (unsigned short)); + + red_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), red_bytes, TRUE); + green_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), green_bytes, TRUE); + blue_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), blue_bytes, TRUE); + + meta_dbus_display_config_complete_get_crtc_gamma (skeleton, invocation, + red_v, green_v, blue_v); + + g_bytes_unref (red_bytes); + g_bytes_unref (green_bytes); + g_bytes_unref (blue_bytes); + + return TRUE; +} + +static gboolean +meta_monitor_manager_handle_set_crtc_gamma (MetaDBusDisplayConfig *skeleton, + GDBusMethodInvocation *invocation, + guint serial, + guint crtc_id, + GVariant *red_v, + GVariant *green_v, + GVariant *blue_v) +{ + MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton); + MetaMonitorManagerClass *klass; + MetaCRTC *crtc; + gsize size, dummy; + unsigned short *red; + unsigned short *green; + unsigned short *blue; + GBytes *red_bytes, *green_bytes, *blue_bytes; + + if (serial != manager->serial) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "The requested configuration is based on stale information"); + return TRUE; + } + + if (crtc_id >= manager->n_crtcs) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Invalid crtc id"); + return TRUE; + } + crtc = &manager->crtcs[crtc_id]; + + red_bytes = g_variant_get_data_as_bytes (red_v); + green_bytes = g_variant_get_data_as_bytes (green_v); + blue_bytes = g_variant_get_data_as_bytes (blue_v); + + size = g_bytes_get_size (red_bytes) / sizeof (unsigned short); + red = (unsigned short*) g_bytes_get_data (red_bytes, &dummy); + green = (unsigned short*) g_bytes_get_data (green_bytes, &dummy); + blue = (unsigned short*) g_bytes_get_data (blue_bytes, &dummy); + + klass = META_MONITOR_MANAGER_GET_CLASS (manager); + if (klass->set_crtc_gamma) + klass->set_crtc_gamma (manager, crtc, size, red, green, blue); + + meta_dbus_display_config_complete_set_crtc_gamma (skeleton, invocation); + + g_bytes_unref (red_bytes); + g_bytes_unref (green_bytes); + g_bytes_unref (blue_bytes); + + return TRUE; +} + +static void +meta_monitor_manager_display_config_init (MetaDBusDisplayConfigIface *iface) +{ + iface->handle_get_resources = meta_monitor_manager_handle_get_resources; + iface->handle_apply_configuration = meta_monitor_manager_handle_apply_configuration; + iface->handle_change_backlight = meta_monitor_manager_handle_change_backlight; + iface->handle_get_crtc_gamma = meta_monitor_manager_handle_get_crtc_gamma; + iface->handle_set_crtc_gamma = meta_monitor_manager_handle_set_crtc_gamma; +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + MetaMonitorManager *manager = user_data; + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager), + connection, + "/org/gnome/Mutter/DisplayConfig", + NULL); +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + meta_topic (META_DEBUG_DBUS, "Acquired name %s\n", name); +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + meta_topic (META_DEBUG_DBUS, "Lost or failed to acquire name %s\n", name); +} + +static void +initialize_dbus_interface (MetaMonitorManager *manager) +{ + manager->dbus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gnome.Mutter.DisplayConfig", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + (meta_get_replace_current_wm () ? + G_BUS_NAME_OWNER_FLAGS_REPLACE : 0), + on_bus_acquired, + on_name_acquired, + on_name_lost, + g_object_ref (manager), + g_object_unref); +} + +static MetaMonitorManager *global_monitor_manager; + +void +meta_monitor_manager_initialize (void) +{ + global_monitor_manager = meta_monitor_manager_new (); +} + +MetaMonitorManager * +meta_monitor_manager_get (void) +{ + g_assert (global_monitor_manager != NULL); + + return global_monitor_manager; +} + +MetaMonitorInfo * +meta_monitor_manager_get_monitor_infos (MetaMonitorManager *manager, + unsigned int *n_infos) +{ + *n_infos = manager->n_monitor_infos; + return manager->monitor_infos; +} + +MetaOutput * +meta_monitor_manager_get_outputs (MetaMonitorManager *manager, + unsigned int *n_outputs) +{ + *n_outputs = manager->n_outputs; + return manager->outputs; +} + +void +meta_monitor_manager_get_resources (MetaMonitorManager *manager, + MetaMonitorMode **modes, + unsigned int *n_modes, + MetaCRTC **crtcs, + unsigned int *n_crtcs, + MetaOutput **outputs, + unsigned int *n_outputs) +{ + *modes = manager->modes; + *n_modes = manager->n_modes; + *crtcs = manager->crtcs; + *n_crtcs = manager->n_crtcs; + *outputs = manager->outputs; + *n_outputs = manager->n_outputs; +} + +int +meta_monitor_manager_get_primary_index (MetaMonitorManager *manager) +{ + return manager->primary_monitor_index; +} + +void +meta_monitor_manager_get_screen_size (MetaMonitorManager *manager, + int *width, + int *height) +{ + *width = manager->screen_width; + *height = manager->screen_height; +} + +void +meta_monitor_manager_get_screen_limits (MetaMonitorManager *manager, + int *width, + int *height) +{ + *width = manager->max_screen_width; + *height = manager->max_screen_height; +} + +static void +invalidate_logical_config (MetaMonitorManager *manager) +{ + MetaMonitorInfo *old_monitor_infos; + + old_monitor_infos = manager->monitor_infos; + + if (manager->in_init) + return; + + make_logical_config (manager); + + g_signal_emit_by_name (manager, "monitors-changed"); + + g_free (old_monitor_infos); +} + +gboolean +meta_monitor_manager_handle_xevent (MetaMonitorManager *manager, + XEvent *event) +{ + MetaMonitorManagerClass *klass; + MetaOutput *old_outputs; + MetaCRTC *old_crtcs; + MetaMonitorMode *old_modes; + int n_old_outputs; + gboolean changed; + + klass = META_MONITOR_MANAGER_GET_CLASS (manager); + if (klass->handle_xevent) + changed = klass->handle_xevent (manager, event); + else + changed = FALSE; + + if (!changed) + return FALSE; + + /* Save the old structures, so they stay valid during the update */ + old_outputs = manager->outputs; + n_old_outputs = manager->n_outputs; + old_modes = manager->modes; + old_crtcs = manager->crtcs; + + read_current_config (manager); + + /* Check if the current intended configuration has the same outputs + as the new real one. If so, this was a result of an ApplyConfiguration + call (or a change from ourselves), and we can go straight to rebuild + the logical config and tell the outside world. + + Otherwise, this event was caused by hotplug, so give a chance to + MetaMonitorConfig. + */ + if (meta_monitor_config_match_current (manager->config, manager)) + { + invalidate_logical_config (manager); + } + else + { + if (!meta_monitor_config_apply_stored (manager->config, manager)) + meta_monitor_config_make_default (manager->config, manager); + } + + free_output_array (old_outputs, n_old_outputs); + g_free (old_modes); + g_free (old_crtcs); + + return TRUE; +} + diff --git a/src/core/screen-private.h b/src/core/screen-private.h index 6bb32f16d..8ddf049b5 100644 --- a/src/core/screen-private.h +++ b/src/core/screen-private.h @@ -38,17 +38,7 @@ #include #include "stack-tracker.h" #include "ui.h" - -typedef struct _MetaMonitorInfo MetaMonitorInfo; - -struct _MetaMonitorInfo -{ - int number; - MetaRectangle rect; - gboolean is_primary; - gboolean in_fullscreen; - XID output; /* The primary or first output for this crtc, None if no xrandr */ -}; +#include "monitor-private.h" typedef void (* MetaScreenWindowFunc) (MetaScreen *screen, MetaWindow *window, gpointer user_data); @@ -101,10 +91,11 @@ struct _MetaScreen Window wm_sn_selection_window; Atom wm_sn_atom; guint32 wm_sn_timestamp; - + MetaMonitorInfo *monitor_infos; - int primary_monitor_index; int n_monitor_infos; + int primary_monitor_index; + gboolean has_xinerama_indices; /* Cache the current monitor */ int last_monitor_index; @@ -232,10 +223,6 @@ void meta_screen_calc_workspace_layout (MetaScreen *screen, MetaWorkspaceLayout *layout); void meta_screen_free_workspace_layout (MetaWorkspaceLayout *layout); -void meta_screen_resize (MetaScreen *screen, - int width, - int height); - void meta_screen_minimize_all_on_active_workspace_except (MetaScreen *screen, MetaWindow *keep); @@ -263,4 +250,9 @@ Window meta_screen_create_guard_window (Display *xdisplay, MetaScreen *screen) gboolean meta_screen_handle_xevent (MetaScreen *screen, XEvent *xevent); +int meta_screen_xinerama_index_to_monitor_index (MetaScreen *screen, + int index); +int meta_screen_monitor_index_to_xinerama_index (MetaScreen *screen, + int index); + #endif diff --git a/src/core/screen.c b/src/core/screen.c index eb3d5264f..b8870da20 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -50,10 +50,6 @@ #include -#ifdef HAVE_RANDR -#include -#endif - #include #include #include @@ -78,6 +74,9 @@ static void meta_screen_sn_event (SnMonitorEvent *event, void *user_data); #endif +static void on_monitors_changed (MetaMonitorManager *manager, + MetaScreen *screen); + enum { PROP_N_WORKSPACES = 1, @@ -352,250 +351,93 @@ set_wm_icon_size_hint (MetaScreen *screen) #undef N_VALS } -/* The list of monitors reported by the windowing system might include - * mirrored monitors with identical bounds. Since mirrored monitors - * shouldn't be treated as separate monitors for most purposes, we - * filter them out here. (We ignore the possibility of partially - * overlapping monitors because they are rare and it's hard to come - * up with any sensible interpretation.) - */ static void -filter_mirrored_monitors (MetaScreen *screen) +meta_screen_ensure_xinerama_indices (MetaScreen *screen) { - int i, j; + XineramaScreenInfo *infos; + int n_infos, i, j; - /* Currently always true and simplifies things */ - g_assert (screen->primary_monitor_index == 0); + if (screen->has_xinerama_indices) + return; - for (i = 1; i < screen->n_monitor_infos; i++) + screen->has_xinerama_indices = TRUE; + + if (!XineramaIsActive (screen->display->xdisplay)) + return; + + infos = XineramaQueryScreens (screen->display->xdisplay, &n_infos); + if (n_infos <= 0 || infos == NULL) { - /* In case we've filtered previous monitors */ - screen->monitor_infos[i].number = i; + meta_XFree (infos); + return; + } - for (j = 0; j < i; j++) + for (i = 0; i < screen->n_monitor_infos; ++i) + { + for (j = 0; j < n_infos; ++j) { - if (meta_rectangle_equal (&screen->monitor_infos[i].rect, - &screen->monitor_infos[j].rect)) - { - memmove (&screen->monitor_infos[i], - &screen->monitor_infos[i + 1], - (screen->n_monitor_infos - i - 1) * sizeof (MetaMonitorInfo)); - screen->n_monitor_infos--; - i--; - - continue; - } + if (screen->monitor_infos[i].rect.x == infos[j].x_org && + screen->monitor_infos[i].rect.y == infos[j].y_org && + screen->monitor_infos[i].rect.width == infos[j].width && + screen->monitor_infos[i].rect.height == infos[j].height) + screen->monitor_infos[i].xinerama_index = j; } } + + meta_XFree (infos); } -#ifdef HAVE_RANDR -static MetaMonitorInfo * -find_monitor_with_rect (MetaScreen *screen, int x, int y, int w, int h) +int +meta_screen_monitor_index_to_xinerama_index (MetaScreen *screen, + int index) +{ + meta_screen_ensure_xinerama_indices (screen); + + return screen->monitor_infos[index].xinerama_index; +} + +int +meta_screen_xinerama_index_to_monitor_index (MetaScreen *screen, + int index) { - MetaMonitorInfo *info; int i; + meta_screen_ensure_xinerama_indices (screen); + for (i = 0; i < screen->n_monitor_infos; i++) - { - info = &screen->monitor_infos[i]; - if (x == info->rect.x && - y == info->rect.y && - w == info->rect.width && - h == info->rect.height) - return info; - } - return NULL; + if (screen->monitor_infos[i].xinerama_index == index) + return i; + + return -1; } -/* In the case of multiple outputs of a single crtc (mirroring), we consider one of the - * outputs the "main". This is the one we consider "owning" the windows, so if - * the mirroring is changed to a dual monitor setup then the windows are moved to the - * crtc that now has that main output. If one of the outputs is the primary that is - * always the main, otherwise we just use the first. - */ -static XID -find_main_output_for_crtc (MetaScreen *screen, XRRScreenResources *resources, XRRCrtcInfo *crtc) -{ - XRROutputInfo *output; - RROutput primary_output; - int i; - XID res; - - primary_output = XRRGetOutputPrimary (screen->display->xdisplay, screen->xroot); - - res = None; - for (i = 0; i < crtc->noutput; i++) - { - output = XRRGetOutputInfo (screen->display->xdisplay, resources, crtc->outputs[i]); - if (output->connection != RR_Disconnected && - (res == None || crtc->outputs[i] == primary_output)) - res = crtc->outputs[i]; - XRRFreeOutputInfo (output); - } - - return res; -} - -#endif - static void reload_monitor_infos (MetaScreen *screen) { - MetaDisplay *display; + GList *tmp; + MetaMonitorManager *manager; - { - GList *tmp; + tmp = screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *space = tmp->data; - tmp = screen->workspaces; - while (tmp != NULL) - { - MetaWorkspace *space = tmp->data; + meta_workspace_invalidate_work_area (space); + + tmp = tmp->next; + } - meta_workspace_invalidate_work_area (space); - - tmp = tmp->next; - } - } + /* Any previous screen->monitor_infos or screen->outputs is freed by the caller */ - display = screen->display; - - /* Any previous screen->monitor_infos is freed by the caller */ - - screen->monitor_infos = NULL; - screen->n_monitor_infos = 0; screen->last_monitor_index = 0; - - /* Xinerama doesn't have a concept of primary monitor, however XRandR - * does. However, the XRandR xinerama compat code always sorts the - * primary output first, so we rely on that here. We could use the - * native XRandR calls instead of xinerama, but that would be - * slightly problematic for _NET_WM_FULLSCREEN_MONITORS support, as - * that is defined in terms of xinerama monitor indexes. - * So, since we don't need anything in xrandr except the primary - * we can keep using xinerama and use the first monitor as the - * primary. - */ - screen->primary_monitor_index = 0; - + screen->has_xinerama_indices = FALSE; screen->display->monitor_cache_invalidated = TRUE; - if (g_getenv ("MUTTER_DEBUG_XINERAMA")) - { - meta_topic (META_DEBUG_XINERAMA, - "Pretending a single monitor has two Xinerama screens\n"); + manager = meta_monitor_manager_get (); - screen->monitor_infos = g_new0 (MetaMonitorInfo, 2); - screen->n_monitor_infos = 2; - - screen->monitor_infos[0].number = 0; - screen->monitor_infos[0].rect = screen->rect; - screen->monitor_infos[0].rect.width = screen->rect.width / 2; - screen->monitor_infos[0].in_fullscreen = -1; - - screen->monitor_infos[1].number = 1; - screen->monitor_infos[1].rect = screen->rect; - screen->monitor_infos[1].rect.x = screen->rect.width / 2; - screen->monitor_infos[1].rect.width = screen->rect.width / 2; - screen->monitor_infos[0].in_fullscreen = -1; - } - - if (screen->n_monitor_infos == 0 && - XineramaIsActive (display->xdisplay)) - { - XineramaScreenInfo *infos; - int n_infos; - int i; - - n_infos = 0; - infos = XineramaQueryScreens (display->xdisplay, &n_infos); - - meta_topic (META_DEBUG_XINERAMA, - "Found %d Xinerama screens on display %s\n", - n_infos, display->name); - - if (n_infos > 0) - { - screen->monitor_infos = g_new0 (MetaMonitorInfo, n_infos); - screen->n_monitor_infos = n_infos; - - i = 0; - while (i < n_infos) - { - screen->monitor_infos[i].number = infos[i].screen_number; - screen->monitor_infos[i].rect.x = infos[i].x_org; - screen->monitor_infos[i].rect.y = infos[i].y_org; - screen->monitor_infos[i].rect.width = infos[i].width; - screen->monitor_infos[i].rect.height = infos[i].height; - screen->monitor_infos[i].in_fullscreen = -1; - - meta_topic (META_DEBUG_XINERAMA, - "Monitor %d is %d,%d %d x %d\n", - screen->monitor_infos[i].number, - screen->monitor_infos[i].rect.x, - screen->monitor_infos[i].rect.y, - screen->monitor_infos[i].rect.width, - screen->monitor_infos[i].rect.height); - - ++i; - } - } - - meta_XFree (infos); - -#ifdef HAVE_RANDR - { - XRRScreenResources *resources; - - resources = XRRGetScreenResourcesCurrent (display->xdisplay, screen->xroot); - if (resources) - { - for (i = 0; i < resources->ncrtc; i++) - { - XRRCrtcInfo *crtc; - MetaMonitorInfo *info; - - crtc = XRRGetCrtcInfo (display->xdisplay, resources, resources->crtcs[i]); - info = find_monitor_with_rect (screen, crtc->x, crtc->y, (int)crtc->width, (int)crtc->height); - if (info) - info->output = find_main_output_for_crtc (screen, resources, crtc); - - XRRFreeCrtcInfo (crtc); - } - XRRFreeScreenResources (resources); - } - } -#endif - } - else if (screen->n_monitor_infos > 0) - { - meta_topic (META_DEBUG_XINERAMA, - "No Xinerama extension or Xinerama inactive on display %s\n", - display->name); - } - - /* If no Xinerama, fill in the single screen info so - * we can use the field unconditionally - */ - if (screen->n_monitor_infos == 0) - { - meta_topic (META_DEBUG_XINERAMA, - "No Xinerama screens, using default screen info\n"); - - screen->monitor_infos = g_new0 (MetaMonitorInfo, 1); - screen->n_monitor_infos = 1; - - screen->monitor_infos[0].number = 0; - screen->monitor_infos[0].rect = screen->rect; - screen->monitor_infos[0].in_fullscreen = -1; - } - - filter_mirrored_monitors (screen); - - screen->monitor_infos[screen->primary_monitor_index].is_primary = TRUE; - - g_assert (screen->n_monitor_infos > 0); - g_assert (screen->monitor_infos != NULL); + screen->monitor_infos = meta_monitor_manager_get_monitor_infos (manager, + (unsigned*)&screen->n_monitor_infos); + screen->primary_monitor_index = meta_monitor_manager_get_primary_index (manager); } /* The guard window allows us to leave minimized windows mapped so @@ -674,6 +516,7 @@ meta_screen_new (MetaDisplay *display, guint32 manager_timestamp; gulong current_workspace; MetaWaylandCompositor *compositor; + MetaMonitorManager *manager; replace_current_wm = meta_get_replace_current_wm (); @@ -833,17 +676,15 @@ meta_screen_new (MetaDisplay *display, screen->xroot = xroot; screen->rect.x = screen->rect.y = 0; - if (meta_is_wayland_compositor ()) - { - compositor = meta_wayland_compositor_get_default (); - screen->rect.width = clutter_actor_get_width (compositor->stage); - screen->rect.height = clutter_actor_get_height (compositor->stage); - } - else - { - screen->rect.width = WidthOfScreen (screen->xscreen); - screen->rect.height = HeightOfScreen (screen->xscreen); - } + meta_monitor_manager_initialize (); + + manager = meta_monitor_manager_get (); + g_signal_connect (manager, "monitors-changed", + G_CALLBACK (on_monitors_changed), screen); + + meta_monitor_manager_get_screen_size (manager, + &screen->rect.width, + &screen->rect.height); screen->current_cursor = -1; /* invalid/unset */ screen->default_xvisual = DefaultVisualOfScreen (screen->xscreen); @@ -869,10 +710,6 @@ meta_screen_new (MetaDisplay *display, screen->compositor_data = NULL; screen->guard_window = None; - screen->monitor_infos = NULL; - screen->n_monitor_infos = 0; - screen->last_monitor_index = 0; - reload_monitor_infos (screen); meta_cursor_tracker_get_for_screen (screen); @@ -959,7 +796,7 @@ meta_screen_new (MetaDisplay *display, meta_verbose ("Added screen %d ('%s') root 0x%lx\n", screen->number, screen->screen_name, screen->xroot); - + return screen; } @@ -3007,19 +2844,15 @@ meta_screen_resize_func (MetaScreen *screen, meta_window_recalc_features (window); } -void -meta_screen_resize (MetaScreen *screen, - int width, - int height) +static void +on_monitors_changed (MetaMonitorManager *manager, + MetaScreen *screen) { - GSList *windows, *tmp; - MetaMonitorInfo *old_monitor_infos; + GSList *tmp, *windows; - screen->rect.width = width; - screen->rect.height = height; - - /* Save the old monitor infos, so they stay valid during the update */ - old_monitor_infos = screen->monitor_infos; + meta_monitor_manager_get_screen_size (manager, + &screen->rect.width, + &screen->rect.height); reload_monitor_infos (screen); set_desktop_geometry_hint (screen); @@ -3031,8 +2864,8 @@ meta_screen_resize (MetaScreen *screen, changes.x = 0; changes.y = 0; - changes.width = width; - changes.height = height; + changes.width = screen->rect.width; + changes.height = screen->rect.height; XConfigureWindow(screen->display->xdisplay, screen->guard_window, @@ -3042,7 +2875,8 @@ meta_screen_resize (MetaScreen *screen, if (screen->display->compositor) meta_compositor_sync_screen_size (screen->display->compositor, - screen, width, height); + screen, + screen->rect.width, screen->rect.height); /* Queue a resize on all the windows */ meta_screen_foreach_window (screen, meta_screen_resize_func, 0); @@ -3058,7 +2892,6 @@ meta_screen_resize (MetaScreen *screen, meta_window_update_for_monitors_changed (window); } - g_free (old_monitor_infos); g_slist_free (windows); meta_screen_queue_check_fullscreen (screen); diff --git a/src/core/util.c b/src/core/util.c index c14fad736..d5014607b 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -345,6 +345,8 @@ topic_name (MetaDebugTopic topic) return "COMPOSITOR"; case META_DEBUG_EDGE_RESISTANCE: return "EDGE_RESISTANCE"; + case META_DEBUG_DBUS: + return "DBUS"; case META_DEBUG_VERBOSE: return "VERBOSE"; } @@ -650,8 +652,13 @@ meta_show_dialog (const char *type, append_argument (args, "zenity"); append_argument (args, type); - append_argument (args, "--display"); - append_argument (args, display); + + if (display) + { + append_argument (args, "--display"); + append_argument (args, display); + } + append_argument (args, "--class"); append_argument (args, "mutter-dialog"); append_argument (args, "--title"); diff --git a/src/core/window-private.h b/src/core/window-private.h index c498aa417..c068c35af 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -179,7 +179,7 @@ struct _MetaWindow * been overridden (via a client message), the window will cover the union of * these monitors. If not, this is the single monitor which the window's * origin is on. */ - long fullscreen_monitors[4]; + gint fullscreen_monitors[4]; /* Whether we're trying to constrain the window to be fully onscreen */ guint require_fully_onscreen : 1; diff --git a/src/core/window.c b/src/core/window.c index 1cafc5ba5..faf4bb2d7 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -2281,10 +2281,14 @@ set_net_wm_state (MetaWindow *window) if (window->fullscreen) { - data[0] = window->fullscreen_monitors[0]; - data[1] = window->fullscreen_monitors[1]; - data[2] = window->fullscreen_monitors[2]; - data[3] = window->fullscreen_monitors[3]; + data[0] = meta_screen_monitor_index_to_xinerama_index (window->screen, + window->fullscreen_monitors[0]); + data[1] = meta_screen_monitor_index_to_xinerama_index (window->screen, + window->fullscreen_monitors[1]); + data[2] = meta_screen_monitor_index_to_xinerama_index (window->screen, + window->fullscreen_monitors[2]); + data[3] = meta_screen_monitor_index_to_xinerama_index (window->screen, + window->fullscreen_monitors[3]); meta_verbose ("Setting _NET_WM_FULLSCREEN_MONITORS\n"); meta_error_trap_push (window->display); @@ -4958,7 +4962,8 @@ meta_window_update_for_monitors_changed (MetaWindow *window) { MetaMonitorInfo *info = &window->screen->monitor_infos[i]; - if (info->output == old->output) + if (info->output_id != 0 && + info->output_id == old->output_id) { new = info; break; @@ -7303,10 +7308,14 @@ meta_window_client_message (MetaWindow *window, meta_verbose ("_NET_WM_FULLSCREEN_MONITORS request for window '%s'\n", window->desc); - top = event->xclient.data.l[0]; - bottom = event->xclient.data.l[1]; - left = event->xclient.data.l[2]; - right = event->xclient.data.l[3]; + top = meta_screen_xinerama_index_to_monitor_index (window->screen, + event->xclient.data.l[0]); + bottom = meta_screen_xinerama_index_to_monitor_index (window->screen, + event->xclient.data.l[1]); + left = meta_screen_xinerama_index_to_monitor_index (window->screen, + event->xclient.data.l[2]); + right = meta_screen_xinerama_index_to_monitor_index (window->screen, + event->xclient.data.l[3]); /* source_indication = event->xclient.data.l[4]; */ meta_window_update_fullscreen_monitors (window, top, bottom, left, right); diff --git a/src/meta/atomnames.h b/src/meta/atomnames.h index 064679914..d7a6e7e35 100644 --- a/src/meta/atomnames.h +++ b/src/meta/atomnames.h @@ -72,6 +72,7 @@ item(_MUTTER_TIMESTAMP_PING) item(_MUTTER_FOCUS_SET) item(_MUTTER_SENTINEL) item(_MUTTER_VERSION) +item(_MUTTER_PRESENTATION_OUTPUT) item(WM_CLIENT_MACHINE) item(MANAGER) item(TARGETS) @@ -79,6 +80,7 @@ item(MULTIPLE) item(TIMESTAMP) item(VERSION) item(ATOM_PAIR) +item(BACKLIGHT) /* Oddities: These are used, and we need atoms for them, * but when we need all _NET_WM hints (i.e. when we're making diff --git a/src/meta/meta-plugin.h b/src/meta/meta-plugin.h index 8999b133e..0587551c8 100644 --- a/src/meta/meta-plugin.h +++ b/src/meta/meta-plugin.h @@ -205,6 +205,21 @@ struct _MetaPluginClass gboolean (*keybinding_filter) (MetaPlugin *plugin, MetaKeyBinding *binding); + /** + * MetaPluginClass::confirm_display_config: + * @plugin: a #MetaPlugin + * + * Virtual function called when the display configuration changes. + * The common way to implement this function is to show some form + * of modal dialog that should ask the user if everything was ok. + * + * When confirmed by the user, the plugin must call meta_plugin_complete_display_change() + * to make the configuration permanent. If that function is not + * called within the timeout, the previous configuration will be + * reapplied. + */ + void (*confirm_display_change) (MetaPlugin *plugin); + /** * MetaPluginClass::plugin_info: * @plugin: a #MetaPlugin @@ -214,6 +229,7 @@ struct _MetaPluginClass * Returns: a #MetaPluginInfo. */ const MetaPluginInfo * (*plugin_info) (MetaPlugin *plugin); + }; /** @@ -360,6 +376,10 @@ void meta_plugin_destroy_completed (MetaPlugin *plugin, MetaWindowActor *actor); +void +meta_plugin_complete_display_change (MetaPlugin *plugin, + gboolean ok); + /** * MetaModalOptions: * @META_MODAL_POINTER_ALREADY_GRABBED: if set the pointer is already diff --git a/src/meta/util.h b/src/meta/util.h index 91f968ba0..cc60fa277 100644 --- a/src/meta/util.h +++ b/src/meta/util.h @@ -102,7 +102,8 @@ typedef enum META_DEBUG_RESIZING = 1 << 18, META_DEBUG_SHAPES = 1 << 19, META_DEBUG_COMPOSITOR = 1 << 20, - META_DEBUG_EDGE_RESISTANCE = 1 << 21 + META_DEBUG_EDGE_RESISTANCE = 1 << 21, + META_DEBUG_DBUS = 1 << 22 } MetaDebugTopic; void meta_topic_real (MetaDebugTopic topic, diff --git a/src/xrandr.xml b/src/xrandr.xml new file mode 100644 index 000000000..06449c36a --- /dev/null +++ b/src/xrandr.xml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +