/* gnome-rr-screen.c: Display information
 *
 * SPDX-FileCopyrightText: 2007, 2008, 2013 Red Hat, Inc.
 * SPDX-FileCopyrightText: 2020 NVIDIA CORPORATION
 * SPDX-License-Identifier: LGPL-2.0-or-later
 * 
 * Author: Soren Sandmann <sandmann@redhat.com>
 *         Giovanni Campagna <gcampagn@redhat.com>
 */

#define GNOME_DESKTOP_USE_UNSTABLE_API

#include "config.h"

#include "gnome-rr-screen.h"

#include "gnome-rr-private.h"

#include <glib/gi18n-lib.h>
#include <string.h>

/* From xf86drmMode.h: it's ABI so it won't change */
#define DRM_MODE_FLAG_INTERLACE                        (1<<4)

enum {
    SCREEN_PROP_0,
    SCREEN_PROP_GDK_DISPLAY,
    SCREEN_PROP_DPMS_MODE,
    SCREEN_PROP_LAST,
};

enum {
    SCREEN_CHANGED,
    SCREEN_OUTPUT_CONNECTED,
    SCREEN_OUTPUT_DISCONNECTED,
    SCREEN_SIGNAL_LAST,
};

static gint screen_signals[SCREEN_SIGNAL_LAST];

struct _GnomeRROutput
{
    ScreenInfo *        info;
    guint                id;
    glong               winsys_id;
    
    char *                name;
    char *                display_name;
    char *                connector_type;
    GnomeRRCrtc *        current_crtc;
    GnomeRRCrtc **        possible_crtcs;
    GnomeRROutput **        clones;
    GnomeRRMode **        modes;

    char *              vendor;
    char *              product;
    char *              serial;
    int                 width_mm;
    int                 height_mm;
    GBytes *            edid;
    char *              edid_file;

    int                 backlight;
    int                 min_backlight_step;

    gboolean            is_primary;
    gboolean            is_presentation;
    gboolean            is_underscanning;
    gboolean            supports_underscanning;
    gboolean            supports_color_transform;

    GnomeRRTile         tile_info;
};

struct _GnomeRRCrtc
{
    ScreenInfo *        info;
    guint                id;
    glong               winsys_id;
    
    GnomeRRMode *        current_mode;
    GnomeRROutput **        current_outputs;
    GnomeRROutput **        possible_outputs;
    int                        x;
    int                        y;
    
    enum wl_output_transform transform;
    int                 all_transforms;
    int                        gamma_size;
};

#define UNDEFINED_MODE_ID 0
struct _GnomeRRMode
{
    ScreenInfo *        info;
    guint                id;
    glong               winsys_id;
    int                        width;
    int                        height;
    int                        freq;                /* in mHz */
    gboolean                tiled;
    guint32             flags;
};

/* GnomeRRCrtc */
static GnomeRRCrtc *  crtc_new          (ScreenInfo         *info,
                                         guint               id);
static GnomeRRCrtc *  crtc_copy         (const GnomeRRCrtc  *from);
static void           crtc_free         (GnomeRRCrtc        *crtc);

static void           crtc_initialize   (GnomeRRCrtc        *crtc,
                                         GVariant           *res);

/* GnomeRROutput */
static GnomeRROutput *output_new        (ScreenInfo         *info,
                                         guint               id);

static void           output_initialize (GnomeRROutput      *output,
                                         GVariant           *res);

static GnomeRROutput *output_copy       (const GnomeRROutput *from);
static void           output_free       (GnomeRROutput      *output);

/* GnomeRRMode */
static GnomeRRMode *  mode_new          (ScreenInfo         *info,
                                         guint               id);

static void           mode_initialize   (GnomeRRMode        *mode,
                                         GVariant           *info);

static GnomeRRMode *  mode_copy         (const GnomeRRMode  *from);
static void           mode_free         (GnomeRRMode        *mode);

static gboolean gnome_rr_screen_initable_init           (GInitable *initable,
                                                         GCancellable *cancellable,
                                                         GError **error);
static void     gnome_rr_screen_initable_iface_init     (GInitableIface *iface);
static void     gnome_rr_screen_async_initable_init     (GAsyncInitableIface *iface);

G_DEFINE_TYPE_WITH_CODE (GnomeRRScreen, gnome_rr_screen, G_TYPE_OBJECT,
                         G_ADD_PRIVATE (GnomeRRScreen)
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gnome_rr_screen_initable_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, gnome_rr_screen_async_initable_init))

G_DEFINE_BOXED_TYPE (GnomeRRCrtc, gnome_rr_crtc, crtc_copy, crtc_free)
G_DEFINE_BOXED_TYPE (GnomeRROutput, gnome_rr_output, output_copy, output_free)
G_DEFINE_BOXED_TYPE (GnomeRRMode, gnome_rr_mode, mode_copy, mode_free)

/* Errors */

/**
 * gnome_rr_error_quark:
 *
 * Returns the error domain used by the GnomeRR API.
 *
 * Return value: the GnomeRR error domain 
 */
G_DEFINE_QUARK (gnome-rr-error-quark, gnome_rr_error)

/* Screen */
static GnomeRROutput *
gnome_rr_output_by_id (ScreenInfo *info, guint id)
{
    GnomeRROutput **output;
    
    g_assert (info != NULL);
    
    for (output = info->outputs; *output; ++output)
    {
        if ((*output)->id == id)
            return *output;
    }
    
    return NULL;
}

static GnomeRRCrtc *
crtc_by_id (ScreenInfo *info, guint id)
{
    GnomeRRCrtc **crtc;
    
    if (!info)
        return NULL;
    
    for (crtc = info->crtcs; *crtc; ++crtc)
    {
        if ((*crtc)->id == id)
            return *crtc;
    }
    
    return NULL;
}

static GnomeRRMode *
mode_by_id (ScreenInfo *info, guint id)
{
    GnomeRRMode **mode;
    
    g_assert (info != NULL);
    
    for (mode = info->modes; *mode; ++mode)
    {
        if ((*mode)->id == id)
            return *mode;
    }
    
    return NULL;
}

static void
screen_info_free (ScreenInfo *info)
{
    GnomeRROutput **output;
    GnomeRRCrtc **crtc;
    GnomeRRMode **mode;
    
    g_assert (info != NULL);

    if (info->outputs)
    {
        for (output = info->outputs; *output; ++output)
            output_free (*output);
        g_free (info->outputs);
    }
    
    if (info->crtcs)
    {
        for (crtc = info->crtcs; *crtc; ++crtc)
            crtc_free (*crtc);
        g_free (info->crtcs);
    }
    
    if (info->modes)
    {
        for (mode = info->modes; *mode; ++mode)
            mode_free (*mode);
        g_free (info->modes);
    }

    if (info->clone_modes)
    {
        /* The modes themselves were freed above */
        g_free (info->clone_modes);
    }
    
    g_free (info);
}

static gboolean
has_similar_mode (GnomeRROutput *output, GnomeRRMode *mode)
{
    int i;
    GnomeRRMode **modes = gnome_rr_output_list_modes (output);
    guint width = gnome_rr_mode_get_width (mode);
    guint height = gnome_rr_mode_get_height (mode);

    for (i = 0; modes[i] != NULL; ++i)
    {
        GnomeRRMode *m = modes[i];

        if (gnome_rr_mode_get_width (m) == width        &&
            gnome_rr_mode_get_height (m) == height)
        {
            return TRUE;
        }
    }

    return FALSE;
}

gboolean
gnome_rr_output_get_tiled_display_size (const GnomeRROutput *output,
                                        int *tile_w,
                                        int *tile_h,
                                        int *total_width,
                                        int *total_height)
{
    GnomeRRTile tile;
    guint ht, vt;
    int i, total_h = 0, total_w = 0;

    if (!gnome_rr_output_get_tile_info (output, &tile))
        return FALSE;

    if (tile.loc_horiz != 0 ||
        tile.loc_vert != 0)
        return FALSE;

    if (tile_w)
        *tile_w = tile.width;
    if (tile_h)
        *tile_h = tile.height;

    for (ht = 0; ht < tile.max_horiz_tiles; ht++) {
        for (vt = 0; vt < tile.max_vert_tiles; vt++) {
            for (i = 0; output->info->outputs[i]; i++) {
                GnomeRRTile this_tile;

                if (!gnome_rr_output_get_tile_info (output->info->outputs[i], &this_tile))
                    continue;

                if (this_tile.group_id != tile.group_id)
                    continue;

                if (this_tile.loc_horiz != ht ||
                    this_tile.loc_vert != vt)
                    continue;

                if (this_tile.loc_horiz == 0)
                    total_h += this_tile.height;

                if (this_tile.loc_vert == 0)
                    total_w += this_tile.width;
            }
        }
    }

    *total_width = total_w;
    *total_height = total_h;

    return TRUE;
}

static void
gather_tile_modes_output (ScreenInfo *info,
                          GnomeRROutput *output)
{
    GPtrArray *a;
    GnomeRRMode *mode;
    int width, height;
    int tile_w, tile_h;
    int i;

    if (!gnome_rr_output_get_tiled_display_size (output, &tile_w, &tile_h, &width, &height))
        return;

    /* now stick the mode into the modelist */
    a = g_ptr_array_new ();
    mode = mode_new (info, UNDEFINED_MODE_ID);
    mode->winsys_id = 0;
    mode->width = width;
    mode->height = height;
    mode->freq = 0;
    mode->tiled = TRUE;

    g_ptr_array_add (a, mode);
    for (i = 0; output->modes[i]; i++)
        g_ptr_array_add (a, output->modes[i]);

    g_ptr_array_add (a, NULL);
    output->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
}

static void
gather_tile_modes (ScreenInfo *info)
{
    int i;

    for (i = 0; info->outputs[i]; i++)
        gather_tile_modes_output (info, info->outputs[i]);
}

static void
gather_clone_modes (ScreenInfo *info)
{
    int i;
    GPtrArray *result = g_ptr_array_new ();

    for (i = 0; info->outputs[i] != NULL; ++i)
    {
        int j;
        GnomeRROutput *output1, *output2;

        output1 = info->outputs[i];
        
        for (j = 0; output1->modes[j] != NULL; ++j)
        {
            GnomeRRMode *mode = output1->modes[j];
            gboolean valid;
            int k;

            valid = TRUE;
            for (k = 0; info->outputs[k] != NULL; ++k)
            {
                output2 = info->outputs[k];
                
                if (!has_similar_mode (output2, mode))
                {
                    valid = FALSE;
                    break;
                }
            }

            if (valid)
                g_ptr_array_add (result, mode);
        }
    }

    g_ptr_array_add (result, NULL);
    
    info->clone_modes = (GnomeRRMode **)g_ptr_array_free (result, FALSE);
}

static void
fill_screen_info_from_resources (ScreenInfo *info,
                                 guint       serial,
                                 GVariant   *crtcs,
                                 GVariant   *outputs,
                                 GVariant   *modes,
                                 int         max_width,
                                 int         max_height)
{
    guint i;
    GPtrArray *a;
    GnomeRRCrtc **crtc;
    GnomeRROutput **output;
    GnomeRRMode **mode;
    guint ncrtc, noutput, nmode;
    guint id;

    info->min_width = 312;
    info->min_height = 312;
    info->max_width = max_width;
    info->max_height = max_height;
    info->serial = serial;

    ncrtc = g_variant_n_children (crtcs);
    noutput = g_variant_n_children (outputs);
    nmode = g_variant_n_children (modes);

    /* We create all the structures before initializing them, so
     * that they can refer to each other.
     */
    a = g_ptr_array_new ();
    for (i = 0; i < ncrtc; ++i)
    {
        g_variant_get_child (crtcs, i, META_CRTC_STRUCT, &id,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

        g_ptr_array_add (a, crtc_new (info, id));
    }
    g_ptr_array_add (a, NULL);
    info->crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);

    a = g_ptr_array_new ();
    for (i = 0; i < noutput; ++i)
    {
        g_variant_get_child (outputs, i, META_OUTPUT_STRUCT, &id,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL);

        g_ptr_array_add (a, output_new (info, id));
    }
    g_ptr_array_add (a, NULL);
    info->outputs = (GnomeRROutput **)g_ptr_array_free (a, FALSE);

    a = g_ptr_array_new ();
    for (i = 0;  i < nmode; ++i)
    {
        g_variant_get_child (modes, i, META_MONITOR_MODE_STRUCT, &id,
                             NULL, NULL, NULL, NULL, NULL);

        g_ptr_array_add (a, mode_new (info, id));
    }
    g_ptr_array_add (a, NULL);
    info->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);

    /* Initialize */
    for (i = 0, crtc = info->crtcs; *crtc; ++i, ++crtc)
    {
        GVariant *child = g_variant_get_child_value (crtcs, i);
        crtc_initialize (*crtc, child);
        g_variant_unref (child);
    }

    for (i = 0, output = info->outputs; *output; ++i, ++output)
    {
        GVariant *child = g_variant_get_child_value (outputs, i);
        output_initialize (*output, child);
        g_variant_unref (child);
    }

    for (i = 0, mode = info->modes; *mode; ++i, ++mode)
    {
        GVariant *child = g_variant_get_child_value (modes, i);
        mode_initialize (*mode, child);
        g_variant_unref (child);
    }

    gather_clone_modes (info);

    gather_tile_modes (info);
}

static gboolean
fill_out_screen_info (ScreenInfo  *info,
                      GError     **error)
{
    GnomeRRScreenPrivate *priv;
    guint serial;
    GVariant *crtcs, *outputs, *modes;
    int max_width, max_height;

    g_assert (info != NULL);

    priv = gnome_rr_screen_get_instance_private (info->screen);

    if (!meta_dbus_display_config_call_get_resources_sync (priv->proxy,
                                                           &serial,
                                                           &crtcs,
                                                           &outputs,
                                                           &modes,
                                                           &max_width,
                                                           &max_height,
                                                           NULL,
                                                           error))
        return FALSE;

    fill_screen_info_from_resources (info, serial, crtcs, outputs,
                                     modes, max_width, max_height);

    g_variant_unref (crtcs);
    g_variant_unref (outputs);
    g_variant_unref (modes);

    return TRUE;
}

static ScreenInfo *
screen_info_new (GnomeRRScreen *screen, GError **error)
{
    ScreenInfo *info = g_new0 (ScreenInfo, 1);

    g_assert (screen != NULL);

    info->outputs = NULL;
    info->crtcs = NULL;
    info->modes = NULL;
    info->screen = screen;
    
    if (fill_out_screen_info (info, error))
    {
        return info;
    }
    else
    {
        screen_info_free (info);
        return NULL;
    }
}

static GnomeRROutput *
find_output_by_winsys_id (GnomeRROutput **haystack, glong winsys_id)
{
    guint i;

    for (i = 0; haystack[i] != NULL; i++)
    {
        if (haystack[i]->winsys_id == winsys_id)
            return haystack[i];
    }
    return NULL;
}

static void
diff_outputs_and_emit_signals (ScreenInfo *old, ScreenInfo *new)
{
    guint i;
    gulong winsys_id_old, winsys_id_new;
    GnomeRROutput *output_old;
    GnomeRROutput *output_new;

    /* have any outputs been removed/disconnected */
    for (i = 0; old->outputs[i] != NULL; i++)
    {
        winsys_id_old = old->outputs[i]->winsys_id;
        output_new = find_output_by_winsys_id (new->outputs, winsys_id_old);
        if (output_new == NULL)
        {
            g_signal_emit (G_OBJECT (new->screen),
                           screen_signals[SCREEN_OUTPUT_DISCONNECTED], 0,
                           old->outputs[i]);
        }
    }

    /* have any outputs been created/connected */
    for (i = 0; new->outputs[i] != NULL; i++)
    {
        winsys_id_new = new->outputs[i]->winsys_id;
        output_old = find_output_by_winsys_id (old->outputs, winsys_id_new);
        if (output_old == NULL)
        {
            g_signal_emit (G_OBJECT (new->screen),
                           screen_signals[SCREEN_OUTPUT_CONNECTED], 0,
                           new->outputs[i]);
        }
    }
}

typedef enum {
    REFRESH_NONE = 0,
    REFRESH_IGNORE_SERIAL = 1,
    REFRESH_FORCE_CALLBACK = 2
} RefreshFlags;

static gboolean
screen_update (GnomeRRScreen *screen, RefreshFlags flags, GError **error)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    ScreenInfo *info;
    gboolean changed = FALSE;
    
    g_assert (screen != NULL);

    info = screen_info_new (screen, error);
    if (!info)
            return FALSE;

    if ((flags & REFRESH_IGNORE_SERIAL) || info->serial != priv->info->serial)
            changed = TRUE;

    /* work out if any outputs have changed connected state */
    diff_outputs_and_emit_signals (priv->info, info);

    screen_info_free (priv->info);
    priv->info = info;

    if (changed || (flags & REFRESH_FORCE_CALLBACK))
        g_signal_emit (G_OBJECT (screen), screen_signals[SCREEN_CHANGED], 0);
    
    return changed;
}

static void
screen_on_monitors_changed (MetaDBusDisplayConfig *proxy,
                            gpointer data)
{
    GnomeRRScreen *screen = data;

    screen_update (screen, REFRESH_FORCE_CALLBACK, NULL);
}

static void
name_owner_changed (GObject       *object,
                    GParamSpec    *pspec,
                    GnomeRRScreen *self)
{
    GError *error;
    char *new_name_owner;

    new_name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
    if (new_name_owner == NULL)
        return;

    error = NULL;
    if (!screen_update (self, REFRESH_IGNORE_SERIAL | REFRESH_FORCE_CALLBACK, &error))
        g_warning ("Failed to refresh screen configuration after mutter was restarted: %s",
                   error->message);

    g_clear_error (&error);
    g_free (new_name_owner);
}

static void
power_save_mode_changed (GObject       *object,
                         GParamSpec    *pspec,
                         GnomeRRScreen *self)
{
        g_object_notify (G_OBJECT (self), "dpms-mode");
}

static gboolean
gnome_rr_screen_initable_init (GInitable *initable, GCancellable *canc, GError **error)
{
    GnomeRRScreen *self = GNOME_RR_SCREEN (initable);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
    MetaDBusDisplayConfig *proxy;

    proxy = meta_dbus_display_config_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                             G_DBUS_PROXY_FLAGS_NONE,
                                                             "org.gnome.Mutter.DisplayConfig",
                                                             "/org/gnome/Mutter/DisplayConfig",
                                                             NULL, error);
    if (!proxy)
        return FALSE;

    priv->proxy = META_DBUS_DISPLAY_CONFIG (proxy);

    priv->info = screen_info_new (self, error);
    if (!priv->info)
        return FALSE;

    g_signal_connect_object (priv->proxy, "notify::g-name-owner",
                             G_CALLBACK (name_owner_changed), self, 0);
    g_signal_connect_object (priv->proxy, "monitors-changed",
                             G_CALLBACK (screen_on_monitors_changed), self, 0);
    g_signal_connect_object (priv->proxy, "notify::power-save-mode",
                             G_CALLBACK (power_save_mode_changed), self, 0);
    return TRUE;
}

static void
on_proxy_acquired (GObject      *object,
                   GAsyncResult *result,
                   gpointer      user_data)
{
    GTask *task = user_data;
    GnomeRRScreen *self = g_task_get_source_object (task);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
    MetaDBusDisplayConfig *proxy;
    GError *error;

    error = NULL;
    proxy = meta_dbus_display_config_proxy_new_for_bus_finish (result, &error);
    if (!proxy)
        return g_task_return_error (task, error);

    priv->proxy = META_DBUS_DISPLAY_CONFIG (proxy);

    priv->info = screen_info_new (self, &error);
    if (!priv->info)
        return g_task_return_error (task, error);

    g_signal_connect_object (priv->proxy, "notify::g-name-owner",
                             G_CALLBACK (name_owner_changed), self, 0);
    g_signal_connect_object (priv->proxy, "monitors-changed",
                             G_CALLBACK (screen_on_monitors_changed), self, 0);
    g_signal_connect_object (priv->proxy, "notify::power-save-mode",
                             G_CALLBACK (power_save_mode_changed), self, 0);
    g_task_return_boolean (task, TRUE);
}

static void
on_name_appeared (GDBusConnection *connection,
                  const char      *name,
                  const char      *name_owner,
                  gpointer         user_data)
{
    GTask *task = user_data;
    GnomeRRScreen *self = g_task_get_source_object (task);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);

    meta_dbus_display_config_proxy_new_for_bus (G_BUS_TYPE_SESSION,
                                                G_DBUS_PROXY_FLAGS_NONE,
                                                "org.gnome.Mutter.DisplayConfig",
                                                "/org/gnome/Mutter/DisplayConfig",
                                                g_task_get_cancellable (task),
                                                on_proxy_acquired, g_object_ref (task));

    g_bus_unwatch_name (priv->init_name_watch_id);
}

static void
gnome_rr_screen_async_initable_init_async (GAsyncInitable      *initable,
                                           int                  io_priority,
                                           GCancellable        *canc,
                                           GAsyncReadyCallback  callback,
                                           gpointer             user_data)
{
    GnomeRRScreen *self = GNOME_RR_SCREEN (initable);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);
    GTask *task;

    task = g_task_new (self, canc, callback, user_data);

    priv->init_name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
                                                 "org.gnome.Mutter.DisplayConfig",
                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                 on_name_appeared,
                                                 NULL,
                                                 task, g_object_unref);
}

static gboolean
gnome_rr_screen_async_initable_init_finish (GAsyncInitable    *initable,
                                            GAsyncResult      *result,
                                            GError           **error)
{
    return g_task_propagate_boolean (G_TASK (result), error);
}

static void
gnome_rr_screen_initable_iface_init (GInitableIface *iface)
{
    iface->init = gnome_rr_screen_initable_init;
}

static void
gnome_rr_screen_async_initable_init (GAsyncInitableIface *iface)
{
    iface->init_async = gnome_rr_screen_async_initable_init_async;
    iface->init_finish = gnome_rr_screen_async_initable_init_finish;
}

void
gnome_rr_screen_finalize (GObject *gobject)
{
    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);

    g_clear_pointer (&priv->info, screen_info_free);
    g_clear_object (&priv->proxy);

    G_OBJECT_CLASS (gnome_rr_screen_parent_class)->finalize (gobject);
}

void
gnome_rr_screen_set_property (GObject *gobject,
                              guint property_id,
                              const GValue *value,
                              GParamSpec *property)
{
    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);

    switch (property_id)
    {
    case SCREEN_PROP_GDK_DISPLAY:
        priv->gdk_display = g_value_get_object (value);
        return;
    case SCREEN_PROP_DPMS_MODE:
        gnome_rr_screen_set_dpms_mode (self, g_value_get_enum (value), NULL);
        return;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
        return;
    }
}

void
gnome_rr_screen_get_property (GObject *gobject,
                              guint property_id,
                              GValue *value,
                              GParamSpec *property)
{
    GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);

    switch (property_id)
    {
    case SCREEN_PROP_GDK_DISPLAY:
        g_value_set_object (value, priv->gdk_display);
        return;
    case SCREEN_PROP_DPMS_MODE: {
        GnomeRRDpmsMode mode;
        if (gnome_rr_screen_get_dpms_mode (self, &mode, NULL))
                g_value_set_enum (value, mode);
        else
                g_value_set_enum (value, GNOME_RR_DPMS_UNKNOWN);
        }
        return;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
        return;
    }
}

void
gnome_rr_screen_class_init (GnomeRRScreenClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

    gobject_class->set_property = gnome_rr_screen_set_property;
    gobject_class->get_property = gnome_rr_screen_get_property;
    gobject_class->finalize = gnome_rr_screen_finalize;

    g_object_class_install_property(
            gobject_class,
            SCREEN_PROP_GDK_DISPLAY,
            g_param_spec_object (
                    "gdk-display",
                    "Display connection",
                    "The GDK display connection represented by this GnomeRRScreen",
                    GDK_TYPE_DISPLAY,
                    G_PARAM_READWRITE |
                    G_PARAM_CONSTRUCT_ONLY |
                    G_PARAM_STATIC_STRINGS)
            );

    g_object_class_install_property(
            gobject_class,
            SCREEN_PROP_DPMS_MODE,
            g_param_spec_enum (
                    "dpms-mode",
                    "DPMS Mode",
                    "The DPMS mode for this GnomeRRScreen",
                    GNOME_RR_TYPE_DPMS_MODE,
                    GNOME_RR_DPMS_UNKNOWN,
                    G_PARAM_READWRITE |
                    G_PARAM_STATIC_STRINGS)
            );

    screen_signals[SCREEN_CHANGED] = g_signal_new("changed",
            G_TYPE_FROM_CLASS (gobject_class),
            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
            G_STRUCT_OFFSET (GnomeRRScreenClass, changed),
            NULL,
            NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE,
            0);

    /**
     * GnomeRRScreen::output-connected:
     * @screen: the #GnomeRRScreen that emitted the signal
     * @output: the #GnomeRROutput that was connected
     *
     * This signal is emitted when a display device is connected to a
     * port, or a port is hotplugged with an active output. The latter
     * can happen if a laptop is docked, and the dock provides a new
     * active output.
     *
     * The @output value is not a #GObject. The returned @output value can
     * only assume to be valid during the emission of the signal (i.e. within
     * your signal handler only), as it may change later when the @screen
     * is modified due to an event from the X server, or due to another
     * place in the application modifying the @screen and the @output.
     * Therefore, deal with changes to the @output right in your signal
     * handler, instead of keeping the @output reference for an async or
     * idle function.
     **/
    screen_signals[SCREEN_OUTPUT_CONNECTED] = g_signal_new("output-connected",
            G_TYPE_FROM_CLASS (gobject_class),
            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
            G_STRUCT_OFFSET (GnomeRRScreenClass, output_connected),
            NULL,
            NULL,
            NULL,
            G_TYPE_NONE,
            1, GNOME_RR_TYPE_OUTPUT);

    /**
     * GnomeRRScreen::output-disconnected:
     * @screen: the #GnomeRRScreen that emitted the signal
     * @output: the #GnomeRROutput that was disconnected
     *
     * This signal is emitted when a display device is disconnected from
     * a port, or a port output is hot-unplugged. The latter can happen
     * if a laptop is undocked, and the dock provided the output.
     *
     * The @output value is not a #GObject. The returned @output value can
     * only assume to be valid during the emission of the signal (i.e. within
     * your signal handler only), as it may change later when the @screen
     * is modified due to an event from the X server, or due to another
     * place in the application modifying the @screen and the @output.
     * Therefore, deal with changes to the @output right in your signal
     * handler, instead of keeping the @output reference for an async or
     * idle function.
     **/
    screen_signals[SCREEN_OUTPUT_DISCONNECTED] = g_signal_new("output-disconnected",
            G_TYPE_FROM_CLASS (gobject_class),
            G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
            G_STRUCT_OFFSET (GnomeRRScreenClass, output_disconnected),
            NULL,
            NULL,
            NULL,
            G_TYPE_NONE,
            1, GNOME_RR_TYPE_OUTPUT);
}

void
gnome_rr_screen_init (GnomeRRScreen *self)
{
}

static MetaDBusDisplayConfig *
gnome_rr_screen_get_dbus_proxy (GnomeRRScreen *self)
{
  GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (self);

  return priv->proxy;
}

/* Weak reference callback set in gnome_rr_screen_new();
 * we remove the GObject data from the GdkDisplay
 */
static void
rr_screen_weak_notify_cb (gpointer data, GObject *where_the_object_was)
{
    GObject *gdk_display = data;

    g_object_set_data (gdk_display, "-gnome-rr-screen-display", NULL);
}

/**
 * gnome_rr_screen_new:
 * @display: the windowing system connection used to query the display data
 * @error: will be set if XRandR is not supported
 *
 * Creates a unique #GnomeRRScreen instance for the specified @display.
 *
 * Returns: a unique #GnomeRRScreen instance, specific to the @screen, or `NULL`
 *   if this could not be created, for instance if the driver does not support
 *   Xrandr 1.2.  Each #GdkDisplay thus has a single instance of #GnomeRRScreen.
 */
GnomeRRScreen *
gnome_rr_screen_new (GdkDisplay *display,
                     GError **error)
{
    GnomeRRScreen *rr_screen;

    g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
    g_return_val_if_fail (error == NULL || *error == NULL, NULL);

    rr_screen = g_object_get_data (G_OBJECT (display), "-gnome-rr-screen-display");
    if (rr_screen) {
        return g_object_ref (rr_screen);
    } else {
        rr_screen = g_initable_new (GNOME_RR_TYPE_SCREEN, NULL, error, "gdk-display", display, NULL);
        if (rr_screen) {
            g_object_set_data (G_OBJECT (display), "-gnome-rr-screen-display", rr_screen);
            g_object_weak_ref (G_OBJECT (rr_screen), rr_screen_weak_notify_cb, display);
        }
    }

    return rr_screen;
}

/**
 * gnome_rr_screen_new_async:
 * @display: the windowing system connection used to query the display
 * @callback: the function to call when the #GnomeRRScreen is ready, or on error
 * @user_data: data to pass to the @callback
 *
 * Asynchronously creates a new #GnomeRRScreen instance.
 *
 * On both success and error, @callback will be invoked. You should use
 * gnome_rr_screen_new_finish() to retrieve the newly created #GnomeRRScreen
 * instance.
 */
void
gnome_rr_screen_new_async (GdkDisplay          *display,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
{
    g_return_if_fail (GDK_IS_DISPLAY (display));

    g_async_initable_new_async (GNOME_RR_TYPE_SCREEN, G_PRIORITY_DEFAULT,
                                NULL, callback, user_data,
                                "gdk-display", display, NULL);
}

/**
 * gnome_rr_screen_new_finish:
 * @result: the result of the asynchronous operation
 * @error: return location for an error
 *
 * Finishes the asynchronous creation of a new #GnomeRRScreen instance.
 *
 * Returns: (transfer full): the newly created instance; on error, this
 *   function will return `NULL` and set the given #GError
 */
GnomeRRScreen *
gnome_rr_screen_new_finish (GAsyncResult  *result,
                            GError       **error)
{
    GObject *source_object;
    GnomeRRScreen *screen;

    source_object = g_async_result_get_source_object (result);
    screen = GNOME_RR_SCREEN (g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error));

    g_object_unref (source_object);

    return screen;
}

/**
 * gnome_rr_screen_get_ranges:
 * @screen: a #GnomeRRScreen
 * @min_width: (out): the minimum width
 * @max_width: (out): the maximum width
 * @min_height: (out): the minimum height
 * @max_height: (out): the maximum height
 *
 * Get the ranges of the screen
 */
void
gnome_rr_screen_get_ranges (GnomeRRScreen *screen,
                            int                  *min_width,
                            int                  *max_width,
                            int           *min_height,
                            int                  *max_height)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    g_return_if_fail (GNOME_RR_IS_SCREEN (screen));
    
    if (min_width)
        *min_width = priv->info->min_width;
    
    if (max_width)
        *max_width = priv->info->max_width;
    
    if (min_height)
        *min_height = priv->info->min_height;
    
    if (max_height)
        *max_height = priv->info->max_height;
}

/**
 * gnome_rr_screen_refresh:
 * @screen: a #GnomeRRScreen
 * @error: location to store error, or %NULL
 *
 * Refreshes the screen configuration, and calls the screen's callback if it
 * exists and if the screen's configuration changed.
 *
 * Return value: TRUE if the screen's configuration changed; otherwise, the
 * function returns FALSE and a NULL error if the configuration didn't change,
 * or FALSE and a non-NULL error if there was an error while refreshing the
 * configuration.
 */
gboolean
gnome_rr_screen_refresh (GnomeRRScreen *screen,
                         GError       **error)
{
    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

    return screen_update (screen, REFRESH_NONE, error);
}

/**
 * gnome_rr_screen_get_dpms_mode:
 * @mode: (out): The current #GnomeRRDpmsMode of this screen
 **/
gboolean
gnome_rr_screen_get_dpms_mode (GnomeRRScreen    *screen,
                               GnomeRRDpmsMode  *mode,
                               GError          **error)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    MetaPowerSave power_save;

    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
    g_return_val_if_fail (mode != NULL, FALSE);

    power_save = meta_dbus_display_config_get_power_save_mode (priv->proxy);
    switch (power_save) {
    case META_POWER_SAVE_UNKNOWN:
        g_set_error_literal (error,
                             GNOME_RR_ERROR,
                             GNOME_RR_ERROR_NO_DPMS_EXTENSION,
                             "Display is not DPMS capable");
        return FALSE;
    case META_POWER_SAVE_ON:
        *mode = GNOME_RR_DPMS_ON;
        break;
    case META_POWER_SAVE_STANDBY:
        *mode = GNOME_RR_DPMS_STANDBY;
        break;
    case META_POWER_SAVE_SUSPEND:
        *mode = GNOME_RR_DPMS_SUSPEND;
        break;
    case META_POWER_SAVE_OFF:
        *mode = GNOME_RR_DPMS_OFF;
        break;
    default:
        g_assert_not_reached ();
        break;
    }

    return TRUE;
}

/**
 * gnome_rr_screen_set_dpms_mode:
 *
 * This method also disables the DPMS timeouts.
 **/
gboolean
gnome_rr_screen_set_dpms_mode (GnomeRRScreen    *screen,
                               GnomeRRDpmsMode   mode,
                               GError          **error)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    MetaPowerSave power_save;

    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

    switch (mode) {
    case GNOME_RR_DPMS_UNKNOWN:
        power_save = META_POWER_SAVE_UNKNOWN;
        break;
    case GNOME_RR_DPMS_ON:
        power_save = META_POWER_SAVE_ON;
        break;
    case GNOME_RR_DPMS_STANDBY:
        power_save = META_POWER_SAVE_STANDBY;
        break;
    case GNOME_RR_DPMS_SUSPEND:
        power_save = META_POWER_SAVE_SUSPEND;
        break;
    case GNOME_RR_DPMS_OFF:
        power_save = META_POWER_SAVE_OFF;
        break;
    default:
        g_assert_not_reached ();
        break;
    }

    meta_dbus_display_config_set_power_save_mode (priv->proxy, power_save);

    return TRUE;
}

/**
 * gnome_rr_screen_list_modes:
 * @screen: the screen to query
 *
 * Lists all available XRandR modes.
 *
 * Returns: (array zero-terminated=1) (transfer none): the available XRandR modes
 */
GnomeRRMode **
gnome_rr_screen_list_modes (GnomeRRScreen *screen)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);
    
    return priv->info->modes;
}

/**
 * gnome_rr_screen_list_clone_modes:
 * @screen: the screen to query
 *
 * Lists all available XRandR clone modes.
 *
 * Returns: (array zero-terminated=1) (transfer none): the available XRandR clone modes
 */
GnomeRRMode **
gnome_rr_screen_list_clone_modes (GnomeRRScreen *screen)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);

    return priv->info->clone_modes;
}

/**
 * gnome_rr_screen_list_crtcs:
 * @screen: the screen to query
 *
 * List all CRTCs of the given screen.
 *
 * Returns: (array zero-terminated=1) (transfer none): the available CRTCs
 */
GnomeRRCrtc **
gnome_rr_screen_list_crtcs (GnomeRRScreen *screen)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);
    
    return priv->info->crtcs;
}

/**
 * gnome_rr_screen_list_outputs:
 * @screen: the screen to query
 *
 * List all outputs of the given screen.
 *
 * Returns: (array zero-terminated=1) (transfer none): the available outputs
 */
GnomeRROutput **
gnome_rr_screen_list_outputs (GnomeRRScreen *screen)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);
    
    return priv->info->outputs;
}

/**
 * gnome_rr_screen_get_crtc_by_id:
 * @screen: the screen to query
 * @id: the identifier of a CRTC
 *
 * Retrieves the CRTC of the screen using the given identifier.
 *
 * Returns: (transfer none): the CRTC identified by @id
 */
GnomeRRCrtc *
gnome_rr_screen_get_crtc_by_id (GnomeRRScreen *screen,
                                guint32        id)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    GnomeRRCrtc **crtcs;
    int i;
    
    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);

    crtcs = priv->info->crtcs;
    
    for (i = 0; crtcs[i] != NULL; ++i)
    {
        if (crtcs[i]->id == id)
            return crtcs[i];
    }
    
    return NULL;
}

/**
 * gnome_rr_screen_get_output_by_id:
 * @screen: the screen to query
 * @id: the identifier of an output
 *
 * Retrieves the output of a screen using the given identifier.
 *
 * Returns: (transfer none): the output identified by @id
 */
GnomeRROutput *
gnome_rr_screen_get_output_by_id (GnomeRRScreen *screen,
                                  guint32        id)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    GnomeRROutput **outputs;
    int i;
    
    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);

    outputs = priv->info->outputs;

    for (i = 0; outputs[i] != NULL; ++i)
    {
        if (outputs[i]->id == id)
            return outputs[i];
    }
    
    return NULL;
}

/* GnomeRROutput */
static GnomeRROutput *
output_new (ScreenInfo *info, guint id)
{
    GnomeRROutput *output = g_slice_new0 (GnomeRROutput);
    
    output->id = id;
    output->info = info;
    
    return output;
}

static void
append_output_array (GnomeRROutput ***array, GnomeRROutput *output)
{
    unsigned i;

    for (i = 0; (*array)[i]; i++);

    *array = g_renew (GnomeRROutput *, *array, i + 2);

    (*array)[i] = output;
    (*array)[i + 1] = NULL;
}

static void
output_initialize (GnomeRROutput *output, GVariant *info)
{
    GPtrArray *a;
    GVariantIter *crtcs, *clones, *modes;
    GVariant *properties, *edid, *tile;
    gint32 current_crtc_id;
    guint32 id;

    g_variant_get (info, META_OUTPUT_STRUCT,
                   &output->id, &output->winsys_id,
                   &current_crtc_id, &crtcs,
                   &output->name,
                   &modes, &clones, &properties);

    /* Possible crtcs */
    a = g_ptr_array_new ();
    while (g_variant_iter_loop (crtcs, "u", &id))
    {
        GnomeRRCrtc *crtc = crtc_by_id (output->info, id);
        
        if (!crtc)
            continue;

        g_ptr_array_add (a, crtc);

        if (current_crtc_id != -1 && crtc->id == (guint32) current_crtc_id)
        {
            output->current_crtc = crtc;
            append_output_array (&crtc->current_outputs, output);
        }

        append_output_array (&crtc->possible_outputs, output);
    }
    g_ptr_array_add (a, NULL);
    output->possible_crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);
    g_variant_iter_free (crtcs);

    /* Clones */
    a = g_ptr_array_new ();
    while (g_variant_iter_loop (clones, "u", &id))
    {
        GnomeRROutput *gnome_rr_output = gnome_rr_output_by_id (output->info, id);
        
        if (gnome_rr_output)
            g_ptr_array_add (a, gnome_rr_output);
    }
    g_ptr_array_add (a, NULL);
    output->clones = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
    g_variant_iter_free (clones);
    
    /* Modes */
    a = g_ptr_array_new ();
    while (g_variant_iter_loop (modes, "u", &id))
    {
        GnomeRRMode *mode = mode_by_id (output->info, id);
        
        if (mode)
            g_ptr_array_add (a, mode);
    }
    g_ptr_array_add (a, NULL);
    output->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
    g_variant_iter_free (modes);

    g_variant_lookup (properties, "vendor", "s", &output->vendor);
    g_variant_lookup (properties, "product", "s", &output->product);
    g_variant_lookup (properties, "serial", "s", &output->serial);
    g_variant_lookup (properties, "width-mm", "i", &output->width_mm);
    g_variant_lookup (properties, "height-mm", "i", &output->height_mm);
    g_variant_lookup (properties, "display-name", "s", &output->display_name);
    g_variant_lookup (properties, "connector-type", "s", &output->connector_type);
    g_variant_lookup (properties, "backlight", "i", &output->backlight);
    g_variant_lookup (properties, "min-backlight-step", "i", &output->min_backlight_step);
    g_variant_lookup (properties, "primary", "b", &output->is_primary);
    g_variant_lookup (properties, "presentation", "b", &output->is_presentation);
    g_variant_lookup (properties, "underscanning", "b", &output->is_underscanning);
    g_variant_lookup (properties, "supports-underscanning", "b", &output->supports_underscanning);
    g_variant_lookup (properties, "supports-color-transform", "b", &output->supports_color_transform);

    if ((edid = g_variant_lookup_value (properties, "edid", G_VARIANT_TYPE ("ay"))))
      {
        output->edid = g_variant_get_data_as_bytes (edid);
        g_variant_unref (edid);
      }
    else
      g_variant_lookup (properties, "edid-file", "s", &output->edid_file);

    if ((tile = g_variant_lookup_value (properties, "tile", G_VARIANT_TYPE ("(uuuuuuuu)"))))
      {
        g_variant_get (tile, "(uuuuuuuu)",
                       &output->tile_info.group_id, &output->tile_info.flags,
                       &output->tile_info.max_horiz_tiles, &output->tile_info.max_vert_tiles,
                       &output->tile_info.loc_horiz, &output->tile_info.loc_vert,
                       &output->tile_info.width, &output->tile_info.height);
        g_variant_unref (tile);
      }
    else
      memset(&output->tile_info, 0, sizeof(output->tile_info));

    if (output->is_primary)
        output->info->primary = output;

    g_variant_unref (properties);
}

static GnomeRROutput*
output_copy (const GnomeRROutput *from)
{
    GPtrArray *array;
    GnomeRRCrtc **p_crtc;
    GnomeRROutput **p_output;
    GnomeRRMode **p_mode;
    GnomeRROutput *output = g_slice_new0 (GnomeRROutput);

    output->id = from->id;
    output->info = from->info;
    output->name = g_strdup (from->name);
    output->display_name = g_strdup (from->display_name);
    output->connector_type = g_strdup (from->connector_type);
    output->vendor = g_strdup (from->vendor);
    output->product = g_strdup (from->product);
    output->serial = g_strdup (from->serial);
    output->current_crtc = from->current_crtc;
    output->backlight = from->backlight;
    if (from->edid)
      output->edid = g_bytes_ref (from->edid);
    output->edid_file = g_strdup (from->edid_file);

    output->is_primary = from->is_primary;
    output->is_presentation = from->is_presentation;

    array = g_ptr_array_new ();
    for (p_crtc = from->possible_crtcs; *p_crtc != NULL; p_crtc++)
    {
        g_ptr_array_add (array, *p_crtc);
    }
    output->possible_crtcs = (GnomeRRCrtc**) g_ptr_array_free (array, FALSE);

    array = g_ptr_array_new ();
    for (p_output = from->clones; *p_output != NULL; p_output++)
    {
        g_ptr_array_add (array, *p_output);
    }
    output->clones = (GnomeRROutput**) g_ptr_array_free (array, FALSE);

    array = g_ptr_array_new ();
    for (p_mode = from->modes; *p_mode != NULL; p_mode++)
    {
        g_ptr_array_add (array, *p_mode);
    }
    output->modes = (GnomeRRMode**) g_ptr_array_free (array, FALSE);

    return output;
}

static void
output_free (GnomeRROutput *output)
{
    g_free (output->clones);
    g_free (output->modes);
    g_free (output->possible_crtcs);
    g_free (output->name);
    g_free (output->vendor);
    g_free (output->product);
    g_free (output->serial);
    g_free (output->display_name);
    g_free (output->connector_type);
    g_free (output->edid_file);
    if (output->edid)
      g_bytes_unref (output->edid);
    g_slice_free (GnomeRROutput, output);
}

guint32
gnome_rr_output_get_id (const GnomeRROutput *output)
{
    g_assert (output != NULL);
    
    return output->id;
}

const guint8 *
gnome_rr_output_get_edid_data (GnomeRROutput *output,
                               gsize         *size)
{
  if (output->edid)
    return g_bytes_get_data (output->edid, size);

  if (output->edid_file)
    {
      GMappedFile *mmap;

      mmap = g_mapped_file_new (output->edid_file, FALSE, NULL);

      if (mmap)
        {
          output->edid = g_mapped_file_get_bytes (mmap);

          g_mapped_file_unref (mmap);

          return g_bytes_get_data (output->edid, size);
        }
    }

  return NULL;
}

/**
 * gnome_rr_output_get_ids_from_edid:
 * @output: the output to query
 * @vendor: (out) (optional) (transfer full): the output's vendor string
 * @product: (out) (optional) (transfer full): the output's product string
 * @serial: (out) (optional) (transfer full): the output's serial string
 *
 * Retrieves the model identifiers from the EDID of the given output.
 */
void
gnome_rr_output_get_ids_from_edid (const GnomeRROutput   *output,
                                   char                 **vendor,
                                   char                 **product,
                                   char                 **serial)
{
    g_return_if_fail (output != NULL);

    if (vendor != NULL)
        *vendor = g_strdup (output->vendor);
    if (product != NULL)
        *product = g_strdup (output->product);
    if (serial != NULL)
        *serial = g_strdup (output->serial);
}

/**
 * gnome_rr_output_get_physical_size:
 * @output: the output to query
 * @width_mm: (out) (optional): the width of the output, in millimeters
 * @height_mm: (out) (optional): the height of the output, in millimeters
 *
 * Retrieves the physical size of the given output.
 */
void
gnome_rr_output_get_physical_size (const GnomeRROutput *output,
                                   int                 *width_mm,
                                   int                 *height_mm)
{
    g_return_if_fail (output != NULL);

    if (width_mm)
        *width_mm = output->width_mm;
    if (height_mm)
        *height_mm = output->height_mm;
}

/**
 * gnome_rr_output_get_display_name:
 * @output: the output to query
 *
 * Retrieves the display name of the given output.
 *
 * Returns: (transfer none): the display name
 */
const char *
gnome_rr_output_get_display_name (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->display_name;
}

/**
 * gnome_rr_output_get_backlight:
 * @output: the output to query
 *
 * Retrieves the backlight brightness of the given output.
 *
 * Returns: The currently set backlight brightness
 */
int
gnome_rr_output_get_backlight (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, -1);

    return output->backlight;
}

/**
 * gnome_rr_output_get_min_backlight_step:
 * @output: the output to query
 *
 * Retrieves the value of the minimum backlight step for the given output,
 * as a percentage.
 *
 * Returns: The minimum backlight step available in percent
 */
int
gnome_rr_output_get_min_backlight_step (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, -1);

    return output->min_backlight_step;
}

/**
 * gnome_rr_output_set_backlight:
 * @output: the output to set
 * @value: the absolute value of the backlight
 *
 * Sets the backlight level for the given output.
 *
 * The value is a percentage, with a range of [0, 100].
 *
 * Returns: `TRUE` for success
 */
gboolean
gnome_rr_output_set_backlight (GnomeRROutput  *output,
                               int             value,
                               GError        **error)
{
    g_return_val_if_fail (output != NULL, FALSE);

    MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (output->info->screen);

    return meta_dbus_display_config_call_change_backlight_sync (proxy,
                                                                output->info->serial,
                                                                output->id, value,
                                                                &output->backlight,
                                                                NULL, error);
}

/**
 * gnome_rr_output_set_color_transform:
 * @output: the output to set
 * @ctm: the color transformation matrix
 * @error: return location for an error
 *
 * Sets the color transformation matrix for the given output.
 *
 * Returns: `TRUE` on success
 */
gboolean
gnome_rr_output_set_color_transform (GnomeRROutput *output,
                                     GnomeRRCTM ctm,
                                     GError **error)
{
    g_return_val_if_fail (output != NULL, FALSE);

    GVariant *values[9];
    for (int i = 0; i < 9; i++)
        values[i] = g_variant_new_uint64 (ctm.matrix[i]);

    GVariant *ctm_var = g_variant_new_tuple (values, 9);

    MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (output->info->screen);
    return meta_dbus_display_config_call_set_output_ctm_sync (proxy,
                                                              output->info->serial,
                                                              output->id,
                                                              ctm_var,
                                                              NULL, error);
}

/**
 * gnome_rr_screen_get_output_by_name:
 * @screen: the screen to query
 *
 * Retrieves the output for the given name.
 *
 * Returns: (transfer none): the output identified by @name
 */
GnomeRROutput *
gnome_rr_screen_get_output_by_name (GnomeRRScreen *screen,
                                    const char    *name)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);
    
    g_return_val_if_fail (GNOME_RR_IS_SCREEN (screen), NULL);
    g_return_val_if_fail (priv->info != NULL, NULL);
    
    for (int i = 0; priv->info->outputs[i] != NULL; ++i)
    {
        GnomeRROutput *output = priv->info->outputs[i];
        
        if (strcmp (output->name, name) == 0)
            return output;
    }
    
    return NULL;
}

/**
 * gnome_rr_output_get_crtc:
 * @output: the output to query
 *
 * Retrieves the CRTC of the given output.
 *
 * Returns: (transfer none): the CRTC of the output
 */
GnomeRRCrtc *
gnome_rr_output_get_crtc (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);
    
    return output->current_crtc;
}

/**
 * gnome_rr_output_get_possible_crtcs:
 * @output: the output to query
 *
 * Retrieves all the possible CRTC for the given output.
 *
 * Returns: (array zero-terminated=1) (transfer none): the list of possible CRTC
 */
GnomeRRCrtc **
gnome_rr_output_get_possible_crtcs (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->possible_crtcs;
}

gboolean
gnome_rr_output_connector_type_is_builtin_display (const char *connector_type)
{
    if (!connector_type)
        return FALSE;

    if (strcmp (connector_type, "LVDS") == 0 ||
        strcmp (connector_type, "eDP") == 0  ||
        strcmp (connector_type, "DSI") == 0)
        return TRUE;

    return FALSE;
}

/**
 * gnome_rr_output_is_builtin_display:
 * @output: the output to query
 *
 * Checks whether the given output is a built-in display.
 *
 * Returns: `TRUE` if the output is a built-in display
 */
gboolean
gnome_rr_output_is_builtin_display (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, FALSE);

    return gnome_rr_output_connector_type_is_builtin_display (output->connector_type);
}

/**
 * gnome_rr_output_get_current_mode:
 * @output: the output to query
 *
 * Retrieves the current mode of the given output.
 *
 * Returns: (transfer none): the current mode of this output
 */
GnomeRRMode *
gnome_rr_output_get_current_mode (const GnomeRROutput *output)
{
    GnomeRRCrtc *crtc;
    GnomeRRMode *mode;

    g_return_val_if_fail (output != NULL, NULL);
    
    if ((crtc = gnome_rr_output_get_crtc (output)))
    {
        int total_w, total_h, tile_w, tile_h;
        mode = gnome_rr_crtc_get_current_mode (crtc);

        if (gnome_rr_output_get_tiled_display_size (output, &tile_w, &tile_h, &total_w, &total_h))
        {
            if (mode->width == tile_w &&
                mode->height == tile_h) {
                if (output->modes[0]->tiled)
                    return output->modes[0];
            }
        }
        return gnome_rr_crtc_get_current_mode (crtc);
    }
    return NULL;
}

/**
 * gnome_rr_output_get_position:
 * @output: the output to query
 * @x: (out) (optional): the X coordinate of the output
 * @y: (out) (optional): the Y coordinate of the output
 */
void
gnome_rr_output_get_position (const GnomeRROutput *output,
                              int                 *x,
                              int                 *y)
{
    GnomeRRCrtc *crtc;
    
    g_return_if_fail (output != NULL);
    
    if ((crtc = gnome_rr_output_get_crtc (output)))
        gnome_rr_crtc_get_position (crtc, x, y);
}

/**
 * gnome_rr_output_get_name:
 * @output: the output to query
 *
 * Retrieves the name of the given output.
 *
 * Returns: (transfer none): the name of the output
 */
const char *
gnome_rr_output_get_name (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->name;
}

/**
 * gnome_rr_output_get_preferred_mode:
 * @output: the output to query
 *
 * Retrieves the preferred mode of the given output.
 *
 * Returns: (transfer none): the preferred mode of the output
 */
GnomeRRMode *
gnome_rr_output_get_preferred_mode (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->modes[0];
}

/**
 * gnome_rr_output_list_modes:
 * @output: the output to query
 *
 * Retrieves all available modes of the given output.
 *
 * Returns: (array zero-terminated=1) (transfer none): a list of modes
 */
GnomeRRMode **
gnome_rr_output_list_modes (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->modes;
}

/**
 * gnome_rr_output_supports_mode:
 * @output: the output to query
 * @mode: the mode to compare
 *
 * Checks whether the given output supports a mode.
 *
 * Returns: `TRUE` if the mode is supported
 */
gboolean
gnome_rr_output_supports_mode (const GnomeRROutput *output,
                               const GnomeRRMode   *mode)
{
    g_return_val_if_fail (output != NULL, FALSE);
    g_return_val_if_fail (mode != NULL, FALSE);
    
    for (int i = 0; output->modes[i] != NULL; ++i)
    {
        if (output->modes[i] == mode)
            return TRUE;
    }
    
    return FALSE;
}

/**
 * gnome_rr_output_can_clone:
 * @output: the output to query
 * @clone: the output to compare
 *
 * Checks whether the given output can clone another output.
 *
 * Returns: `TRUE` if the output can clone another output
 */
gboolean
gnome_rr_output_can_clone (const GnomeRROutput *output,
                           const GnomeRROutput *clone)
{
    g_return_val_if_fail (output != NULL, FALSE);
    g_return_val_if_fail (clone != NULL, FALSE);
    
    for (int i = 0; output->clones[i] != NULL; ++i)
    {
        if (output->clones[i] == clone)
            return TRUE;
    }
    
    return FALSE;
}

/**
 * gnome_rr_output_get_is_primary:
 * @output: the output to query
 *
 * Checks whether the given output is the primary output.
 *
 * Returns: `TRUE` if the output is the primary one
 */
gboolean
gnome_rr_output_get_is_primary (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, FALSE);

    return output->is_primary;
}

/* GnomeRRCrtc */
static const GnomeRRRotation rotation_map[] =
{
    GNOME_RR_ROTATION_0,
    GNOME_RR_ROTATION_90,
    GNOME_RR_ROTATION_180,
    GNOME_RR_ROTATION_270,
    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_0,
    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_90,
    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_180,
    GNOME_RR_REFLECT_X | GNOME_RR_ROTATION_270,
};

static GnomeRRRotation
gnome_rr_rotation_from_transform (enum wl_output_transform transform)
{
    return rotation_map[transform];
}

/**
 * gnome_rr_crtc_get_current_mode:
 * @crtc: a #GnomeRRCrtc
 * Returns: (transfer none): the current mode of this crtc
 */
GnomeRRMode *
gnome_rr_crtc_get_current_mode (GnomeRRCrtc *crtc)
{
    g_return_val_if_fail (crtc != NULL, NULL);
    
    return crtc->current_mode;
}

guint32
gnome_rr_crtc_get_id (GnomeRRCrtc *crtc)
{
    g_return_val_if_fail (crtc != NULL, 0);
    
    return crtc->id;
}

gboolean
gnome_rr_crtc_can_drive_output (GnomeRRCrtc   *crtc,
                                GnomeRROutput *output)
{
    int i;
    
    g_return_val_if_fail (crtc != NULL, FALSE);
    g_return_val_if_fail (output != NULL, FALSE);
    
    for (i = 0; crtc->possible_outputs[i] != NULL; ++i)
    {
        if (crtc->possible_outputs[i] == output)
            return TRUE;
    }
    
    return FALSE;
}

/**
 * gnome_rr_crtc_get_position:
 * @crtc: a #GnomeRRCrtc
 * @x: (out) (allow-none):
 * @y: (out) (allow-none):
 */
void
gnome_rr_crtc_get_position (GnomeRRCrtc *crtc,
                            int         *x,
                            int         *y)
{
    g_return_if_fail (crtc != NULL);
    
    if (x)
        *x = crtc->x;
    
    if (y)
        *y = crtc->y;
}

GnomeRRRotation
gnome_rr_crtc_get_current_rotation (GnomeRRCrtc *crtc)
{
    g_assert(crtc != NULL);
    return gnome_rr_rotation_from_transform (crtc->transform);
}

static GnomeRRRotation
gnome_rr_rotation_from_all_transforms (int all_transforms)
{
    GnomeRRRotation ret = all_transforms & 0xF;

    if (all_transforms & (1 << WL_OUTPUT_TRANSFORM_FLIPPED))
        ret |= GNOME_RR_REFLECT_X;

    if (all_transforms & (1 << WL_OUTPUT_TRANSFORM_FLIPPED_180))
        ret |= GNOME_RR_REFLECT_Y;

    return ret;
}

GnomeRRRotation
gnome_rr_crtc_get_rotations (GnomeRRCrtc *crtc)
{
    g_assert(crtc != NULL);
    return gnome_rr_rotation_from_all_transforms (crtc->all_transforms);
}

gboolean
gnome_rr_crtc_supports_rotation (GnomeRRCrtc *   crtc,
                                 GnomeRRRotation rotation)
{
    g_return_val_if_fail (crtc != NULL, FALSE);
    return (gnome_rr_rotation_from_all_transforms (crtc->all_transforms) & rotation);
}

static GnomeRRCrtc *
crtc_new (ScreenInfo *info, guint id)
{
    GnomeRRCrtc *crtc = g_slice_new0 (GnomeRRCrtc);
    
    crtc->id = id;
    crtc->info = info;
    crtc->current_outputs = g_new0 (GnomeRROutput *, 1);
    crtc->possible_outputs = g_new0 (GnomeRROutput *, 1);
    
    return crtc;
}

static GnomeRRCrtc *
crtc_copy (const GnomeRRCrtc *from)
{
    GnomeRROutput **p_output;
    GPtrArray *array;
    GnomeRRCrtc *to = g_slice_new0 (GnomeRRCrtc);

    to->info = from->info;
    to->id = from->id;
    to->current_mode = from->current_mode;
    to->x = from->x;
    to->y = from->y;
    to->transform = from->transform;
    to->all_transforms = from->all_transforms;
    to->gamma_size = from->gamma_size;

    array = g_ptr_array_new ();
    for (p_output = from->current_outputs; *p_output != NULL; p_output++)
    {
        g_ptr_array_add (array, *p_output);
    }
    to->current_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);

    array = g_ptr_array_new ();
    for (p_output = from->possible_outputs; *p_output != NULL; p_output++)
    {
        g_ptr_array_add (array, *p_output);
    }
    to->possible_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);

    return to;
}

static void
crtc_initialize (GnomeRRCrtc *crtc, GVariant *info)
{
    GVariantIter *all_transforms;
    int current_mode_id;
    guint transform;

    g_variant_get (info, META_CRTC_STRUCT,
                   &crtc->id, &crtc->winsys_id,
                   &crtc->x, &crtc->y,
                   NULL, NULL,
                   &current_mode_id,
                   &crtc->transform, &all_transforms,
                   NULL);

    if (current_mode_id >= 0)
      crtc->current_mode = mode_by_id (crtc->info, current_mode_id);
    
    while (g_variant_iter_loop (all_transforms, "u", &transform))
        crtc->all_transforms |= 1 << transform;
    g_variant_iter_free (all_transforms);
}

static void
crtc_free (GnomeRRCrtc *crtc)
{
    g_free (crtc->current_outputs);
    g_free (crtc->possible_outputs);
    g_slice_free (GnomeRRCrtc, crtc);
}

/* GnomeRRMode */
static GnomeRRMode *
mode_new (ScreenInfo *info, guint id)
{
    GnomeRRMode *mode = g_slice_new0 (GnomeRRMode);
    
    mode->id = id;
    mode->info = info;
    
    return mode;
}

guint32
gnome_rr_mode_get_id (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0);
    return mode->id;
}

guint
gnome_rr_mode_get_width (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0);
    return mode->width;
}

int
gnome_rr_mode_get_freq (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0);
    return (mode->freq) / 1000;
}

double
gnome_rr_mode_get_freq_f (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0.0);
    return (mode->freq) / 1000.0;
}

guint
gnome_rr_mode_get_height (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0);
    return mode->height;
}

/**
 * gnome_rr_mode_get_is_tiled:
 * @mode: a #GnomeRRMode
 *
 * Returns TRUE if this mode is a tiled
 * mode created for span a tiled monitor.
 */
gboolean
gnome_rr_mode_get_is_tiled (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, FALSE);
    return mode->tiled;
}

gboolean
gnome_rr_mode_get_is_interlaced (GnomeRRMode *mode)
{
    g_return_val_if_fail (mode != NULL, 0);
    return (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0;
}

static void
mode_initialize (GnomeRRMode *mode, GVariant *info)
{
    gdouble frequency;

    g_variant_get (info, META_MONITOR_MODE_STRUCT,
                   &mode->id, &mode->winsys_id,
                   &mode->width, &mode->height,
                   &frequency, &mode->flags);
    
    mode->freq = frequency * 1000;
}

static GnomeRRMode *
mode_copy (const GnomeRRMode *from)
{
    GnomeRRMode *to = g_slice_new0 (GnomeRRMode);

    to->id = from->id;
    to->info = from->info;
    to->width = from->width;
    to->height = from->height;
    to->freq = from->freq;

    return to;
}

static void
mode_free (GnomeRRMode *mode)
{
    g_slice_free (GnomeRRMode, mode);
}

gboolean
gnome_rr_screen_apply_configuration (GnomeRRScreen  *screen,
                                     gboolean        persistent,
                                     GVariant       *crtcs,
                                     GVariant       *outputs,
                                     GError        **error)
{
    GnomeRRScreenPrivate *priv = gnome_rr_screen_get_instance_private (screen);

    return meta_dbus_display_config_call_apply_configuration_sync (priv->proxy,
                                                                   priv->info->serial,
                                                                   persistent,
                                                                   crtcs, outputs,
                                                                   NULL, error);
}

gboolean
gnome_rr_crtc_set_gamma (GnomeRRCrtc    *crtc,
                         int             size,
                         unsigned short *red,
                         unsigned short *green,
                         unsigned short *blue)
{
  GBytes *red_bytes = g_bytes_new (red, size * sizeof (unsigned short));
  GBytes *green_bytes = g_bytes_new (green, size * sizeof (unsigned short));
  GBytes *blue_bytes = g_bytes_new (blue, size * sizeof (unsigned short));

  GVariant *red_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), red_bytes, TRUE);
  GVariant *green_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), green_bytes, TRUE);
  GVariant *blue_v = g_variant_new_from_bytes (G_VARIANT_TYPE ("aq"), blue_bytes, TRUE);

  MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (crtc->info->screen);

  gboolean res =
    meta_dbus_display_config_call_set_crtc_gamma_sync (proxy,
                                                       crtc->info->serial,
                                                       crtc->id,
                                                       red_v,
                                                       green_v,
                                                       blue_v,
                                                       NULL, NULL);

  g_bytes_unref (red_bytes);
  g_bytes_unref (green_bytes);
  g_bytes_unref (blue_bytes);
  /* The variant above are floating, no need to free them */

  return res;
}

/**
 * gnome_rr_crtc_get_gamma:
 * @crtc: a #GnomeRRCrtc
 * @size:
 * @red: (out): the minimum width
 * @green: (out): the maximum width
 * @blue: (out): the minimum height
 *
 * Returns: %TRUE for success
 */
gboolean
gnome_rr_crtc_get_gamma (GnomeRRCrtc     *crtc,
                         int             *size,
                         unsigned short **red,
                         unsigned short **green,
                         unsigned short **blue)
{
  GBytes *red_bytes, *green_bytes, *blue_bytes;
  GVariant *red_v, *green_v, *blue_v;
  gboolean ok;
  gsize dummy;

  g_return_val_if_fail (crtc != NULL, FALSE);

  MetaDBusDisplayConfig *proxy = gnome_rr_screen_get_dbus_proxy (crtc->info->screen);
  ok = meta_dbus_display_config_call_get_crtc_gamma_sync (proxy,
                                                          crtc->info->serial,
                                                          crtc->id,
                                                          &red_v,
                                                          &green_v,
                                                          &blue_v,
                                                          NULL, NULL);
  if (!ok)
    return FALSE;

  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);

  /* Unref the variant early so that the bytes hold the only reference to
     the data and we don't need to copy
  */
  g_variant_unref (red_v);
  g_variant_unref (green_v);
  g_variant_unref (blue_v);

  if (size)
    *size = g_bytes_get_size (red_bytes) / sizeof (unsigned short);

  if (red)
    *red = g_bytes_unref_to_data (red_bytes, &dummy);
  else
    g_bytes_unref (red_bytes);
  if (green)
    *green = g_bytes_unref_to_data (green_bytes, &dummy);
  else
    g_bytes_unref (green_bytes);
  if (blue)
    *blue = g_bytes_unref_to_data (blue_bytes, &dummy);
  else
    g_bytes_unref (blue_bytes);

  return TRUE;
}

gboolean
gnome_rr_output_get_is_underscanning (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, FALSE);

    return output->is_underscanning;
}

gboolean
gnome_rr_output_supports_underscanning (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, FALSE);

    return output->supports_underscanning;
}

gboolean
gnome_rr_output_supports_color_transform (const GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, FALSE);

    return output->supports_color_transform;
}

const char *
gnome_rr_output_get_connector_type (GnomeRROutput *output)
{
    g_return_val_if_fail (output != NULL, NULL);

    return output->connector_type;
}

gboolean
gnome_rr_output_get_tile_info (const GnomeRROutput *output,
                               GnomeRRTile *tile)
{
    g_return_val_if_fail (output != NULL, FALSE);

    if (output->tile_info.group_id == UNDEFINED_GROUP_ID)
        return FALSE;

    if (!tile)
        return FALSE;

    *tile = output->tile_info;
    return TRUE;
}

GType
gnome_rr_dpms_mode_get_type (void)
{
  static GType etype = 0;

  if (g_once_init_enter (&etype)) {
    static const GEnumValue values[] = {
      { GNOME_RR_DPMS_ON, "GNOME_RR_DPMS_ON", "on" },
      { GNOME_RR_DPMS_STANDBY, "GNOME_RR_DPMS_STANDBY", "standby" },
      { GNOME_RR_DPMS_SUSPEND, "GNOME_RR_DPMS_SUSPEND", "suspend" },
      { GNOME_RR_DPMS_OFF, "GNOME_RR_DPMS_OFF", "off" },
      { GNOME_RR_DPMS_UNKNOWN, "GNOME_RR_DPMS_UNKNOWN", "unknown" },
      { 0, NULL, NULL }
    };
    GType g_define_type = g_enum_register_static ("GnomeRRDpmsModeType", values);

    g_once_init_leave (&etype, g_define_type);
  }
  return etype;
}
