MonitorConfig: add CRTC assignment
Ripped off libgnome-desktop, trimming the parts that checked that the configuration was plausible, as that should be done in gnome-control-center before asking mutter for a change. https://bugzilla.gnome.org/show_bug.cgi?id=705670
This commit is contained in:
parent
8f4621240a
commit
d0529b7482
@ -25,6 +25,15 @@
|
|||||||
* 02111-1307, USA.
|
* 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 <sandmann@redhat.com>
|
||||||
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -90,6 +99,11 @@ struct _MetaMonitorConfigClass {
|
|||||||
|
|
||||||
G_DEFINE_TYPE (MetaMonitorConfig, meta_monitor_config, G_TYPE_OBJECT);
|
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
|
static void
|
||||||
free_output_key (MetaOutputKey *key)
|
free_output_key (MetaOutputKey *key)
|
||||||
{
|
{
|
||||||
@ -774,35 +788,37 @@ meta_monitor_config_get_stored (MetaMonitorConfig *self,
|
|||||||
return stored;
|
return stored;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static gboolean
|
||||||
make_crtcs (MetaConfiguration *config,
|
|
||||||
MetaMonitorManager *manager,
|
|
||||||
GVariant **crtcs,
|
|
||||||
GVariant **outputs)
|
|
||||||
{
|
|
||||||
*crtcs = NULL;
|
|
||||||
*outputs = NULL;
|
|
||||||
/* FIXME */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
apply_configuration (MetaMonitorConfig *self,
|
apply_configuration (MetaMonitorConfig *self,
|
||||||
MetaConfiguration *config,
|
MetaConfiguration *config,
|
||||||
MetaMonitorManager *manager,
|
MetaMonitorManager *manager,
|
||||||
gboolean stored)
|
gboolean stored)
|
||||||
{
|
{
|
||||||
GVariant *crtcs, *outputs;
|
GPtrArray *crtcs, *outputs;
|
||||||
|
|
||||||
make_crtcs (config, manager, &crtcs, &outputs);
|
crtcs = g_ptr_array_new_full (config->n_outputs, (GDestroyNotify)meta_crtc_info_free);
|
||||||
meta_monitor_manager_apply_configuration (manager, crtcs, outputs);
|
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);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta_monitor_manager_apply_configuration (manager,
|
||||||
|
(MetaCRTCInfo**)crtcs->pdata, crtcs->len,
|
||||||
|
(MetaOutputInfo**)outputs->pdata, outputs->len);
|
||||||
|
|
||||||
if (self->current && !self->current_is_stored)
|
if (self->current && !self->current_is_stored)
|
||||||
config_free (self->current);
|
config_free (self->current);
|
||||||
self->current = config;
|
self->current = config;
|
||||||
self->current_is_stored = stored;
|
self->current_is_stored = stored;
|
||||||
|
|
||||||
g_variant_unref (crtcs);
|
g_ptr_array_unref (crtcs);
|
||||||
g_variant_unref (outputs);
|
g_ptr_array_unref (outputs);
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
@ -818,8 +834,7 @@ meta_monitor_config_apply_stored (MetaMonitorConfig *self,
|
|||||||
|
|
||||||
if (stored)
|
if (stored)
|
||||||
{
|
{
|
||||||
apply_configuration (self, stored, manager, TRUE);
|
return apply_configuration (self, stored, manager, TRUE);
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -1039,3 +1054,329 @@ meta_monitor_config_make_persistent (MetaMonitorConfig *self)
|
|||||||
|
|
||||||
meta_monitor_config_save (self);
|
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:
|
||||||
|
if (!success)
|
||||||
|
meta_warning ("Could not assign CRTC to outputs, ignoring configuration\n");
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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[0];
|
||||||
|
|
||||||
|
output_info->output = find_output_by_key (all_outputs, n_outputs,
|
||||||
|
&config->keys[0]);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
@ -65,6 +65,8 @@ typedef struct _MetaOutput MetaOutput;
|
|||||||
typedef struct _MetaCRTC MetaCRTC;
|
typedef struct _MetaCRTC MetaCRTC;
|
||||||
typedef struct _MetaMonitorMode MetaMonitorMode;
|
typedef struct _MetaMonitorMode MetaMonitorMode;
|
||||||
typedef struct _MetaMonitorInfo MetaMonitorInfo;
|
typedef struct _MetaMonitorInfo MetaMonitorInfo;
|
||||||
|
typedef struct _MetaCRTCInfo MetaCRTCInfo;
|
||||||
|
typedef struct _MetaOutputInfo MetaOutputInfo;
|
||||||
|
|
||||||
struct _MetaOutput
|
struct _MetaOutput
|
||||||
{
|
{
|
||||||
@ -161,6 +163,33 @@ struct _MetaMonitorInfo
|
|||||||
glong output_id;
|
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_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(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_MONITOR_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_MONITOR_MANAGER, MetaMonitorManagerClass))
|
||||||
@ -182,6 +211,14 @@ MetaMonitorInfo *meta_monitor_manager_get_monitor_infos (MetaMonitorManager *
|
|||||||
MetaOutput *meta_monitor_manager_get_outputs (MetaMonitorManager *manager,
|
MetaOutput *meta_monitor_manager_get_outputs (MetaMonitorManager *manager,
|
||||||
unsigned int *n_outputs);
|
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);
|
int meta_monitor_manager_get_primary_index (MetaMonitorManager *manager);
|
||||||
|
|
||||||
gboolean meta_monitor_manager_handle_xevent (MetaMonitorManager *manager,
|
gboolean meta_monitor_manager_handle_xevent (MetaMonitorManager *manager,
|
||||||
@ -191,9 +228,11 @@ void meta_monitor_manager_get_screen_size (MetaMonitorManager *
|
|||||||
int *width,
|
int *width,
|
||||||
int *height);
|
int *height);
|
||||||
|
|
||||||
void meta_monitor_manager_apply_configuration (MetaMonitorManager *manager,
|
void meta_monitor_manager_apply_configuration (MetaMonitorManager *manager,
|
||||||
GVariant *crtcs,
|
MetaCRTCInfo **crtcs,
|
||||||
GVariant *outputs);
|
unsigned int n_crtcs,
|
||||||
|
MetaOutputInfo **outputs,
|
||||||
|
unsigned int n_outputs);
|
||||||
|
|
||||||
#define META_TYPE_MONITOR_CONFIG (meta_monitor_config_get_type ())
|
#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(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_MONITOR_CONFIG, MetaMonitorConfig))
|
||||||
@ -222,6 +261,9 @@ void meta_monitor_config_update_current (MetaMonitorConfig *confi
|
|||||||
MetaMonitorManager *manager);
|
MetaMonitorManager *manager);
|
||||||
void meta_monitor_config_make_persistent (MetaMonitorConfig *config);
|
void meta_monitor_config_make_persistent (MetaMonitorConfig *config);
|
||||||
|
|
||||||
|
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
|
/* Returns true if transform causes width and height to be inverted
|
||||||
This is true for the odd transforms in the enum */
|
This is true for the odd transforms in the enum */
|
||||||
static inline gboolean
|
static inline gboolean
|
||||||
|
@ -1160,22 +1160,20 @@ wl_transform_to_xrandr (enum wl_output_transform transform)
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
apply_config_xrandr (MetaMonitorManager *manager,
|
apply_config_xrandr (MetaMonitorManager *manager,
|
||||||
GVariantIter *crtcs,
|
MetaCRTCInfo **crtcs,
|
||||||
GVariantIter *outputs)
|
unsigned int n_crtcs,
|
||||||
|
MetaOutputInfo **outputs,
|
||||||
|
unsigned int n_outputs)
|
||||||
{
|
{
|
||||||
GVariant *nested_outputs, *properties;
|
|
||||||
guint crtc_id, output_id, transform;
|
|
||||||
int new_mode, x, y;
|
|
||||||
unsigned i;
|
unsigned i;
|
||||||
|
|
||||||
while (g_variant_iter_loop (crtcs, "(uiiiu@aua{sv})",
|
for (i = 0; i < n_crtcs; i++)
|
||||||
&crtc_id, &new_mode, &x, &y,
|
|
||||||
&transform, &nested_outputs, NULL))
|
|
||||||
{
|
{
|
||||||
MetaCRTC *crtc = &manager->crtcs[crtc_id];
|
MetaCRTCInfo *crtc_info = crtcs[i];
|
||||||
|
MetaCRTC *crtc = crtc_info->crtc;
|
||||||
crtc->is_dirty = TRUE;
|
crtc->is_dirty = TRUE;
|
||||||
|
|
||||||
if (new_mode == -1)
|
if (crtc_info->mode == NULL)
|
||||||
{
|
{
|
||||||
XRRSetCrtcConfig (manager->xdisplay,
|
XRRSetCrtcConfig (manager->xdisplay,
|
||||||
manager->resources,
|
manager->resources,
|
||||||
@ -1190,54 +1188,47 @@ apply_config_xrandr (MetaMonitorManager *manager,
|
|||||||
{
|
{
|
||||||
MetaMonitorMode *mode;
|
MetaMonitorMode *mode;
|
||||||
XID *outputs;
|
XID *outputs;
|
||||||
int i, n_outputs;
|
int j, n_outputs;
|
||||||
guint output_id;
|
|
||||||
Status ok;
|
Status ok;
|
||||||
|
|
||||||
mode = &manager->modes[new_mode];
|
mode = crtc_info->mode;
|
||||||
|
|
||||||
n_outputs = g_variant_n_children (nested_outputs);
|
n_outputs = crtc_info->outputs->len;
|
||||||
outputs = g_new (XID, n_outputs);
|
outputs = g_new (XID, n_outputs);
|
||||||
|
|
||||||
for (i = 0; i < n_outputs; i++)
|
for (j = 0; j < n_outputs; j++)
|
||||||
{
|
outputs[i] = ((MetaOutput**)crtc_info->outputs->pdata)[i]->output_id;
|
||||||
g_variant_get_child (nested_outputs, i, "u", &output_id);
|
|
||||||
|
|
||||||
outputs[i] = manager->outputs[output_id].output_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
meta_error_trap_push (meta_get_display ());
|
meta_error_trap_push (meta_get_display ());
|
||||||
ok = XRRSetCrtcConfig (manager->xdisplay,
|
ok = XRRSetCrtcConfig (manager->xdisplay,
|
||||||
manager->resources,
|
manager->resources,
|
||||||
(XID)crtc->crtc_id,
|
(XID)crtc->crtc_id,
|
||||||
manager->time,
|
manager->time,
|
||||||
x, y,
|
crtc_info->x, crtc_info->y,
|
||||||
(XID)mode->mode_id,
|
(XID)mode->mode_id,
|
||||||
wl_transform_to_xrandr (transform),
|
wl_transform_to_xrandr (crtc_info->transform),
|
||||||
outputs, n_outputs);
|
outputs, n_outputs);
|
||||||
meta_error_trap_pop (meta_get_display ());
|
meta_error_trap_pop (meta_get_display ());
|
||||||
|
|
||||||
if (ok != Success)
|
if (ok != Success)
|
||||||
meta_warning ("Configuring CRTC %d with mode %d (%d x %d @ %f) at position %d, %d and transfrom %u failed\n",
|
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),
|
(unsigned)(crtc->crtc_id), (unsigned)(mode->mode_id),
|
||||||
mode->width, mode->height, (float)mode->refresh_rate, x, y, transform);
|
mode->width, mode->height, (float)mode->refresh_rate,
|
||||||
|
crtc_info->x, crtc_info->y, crtc_info->transform);
|
||||||
|
|
||||||
g_free (outputs);
|
g_free (outputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (g_variant_iter_loop (outputs, "(u@a{sv})",
|
for (i = 0; i < n_outputs; i++)
|
||||||
&output_id, &properties))
|
|
||||||
{
|
{
|
||||||
gboolean primary;
|
MetaOutputInfo *output_info = outputs[i];
|
||||||
|
|
||||||
if (g_variant_lookup (properties, "primary", "b", &primary) && primary)
|
if (output_info->is_primary)
|
||||||
{
|
{
|
||||||
MetaOutput *output = &manager->outputs[output_id];
|
|
||||||
|
|
||||||
XRRSetOutputPrimary (manager->xdisplay,
|
XRRSetOutputPrimary (manager->xdisplay,
|
||||||
DefaultRootWindow (manager->xdisplay),
|
DefaultRootWindow (manager->xdisplay),
|
||||||
(XID)output->output_id);
|
(XID)output_info->output->output_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,23 +1259,21 @@ apply_config_xrandr (MetaMonitorManager *manager,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
apply_config_dummy (MetaMonitorManager *manager,
|
apply_config_dummy (MetaMonitorManager *manager,
|
||||||
GVariantIter *crtcs,
|
MetaCRTCInfo **crtcs,
|
||||||
GVariantIter *outputs)
|
unsigned int n_crtcs,
|
||||||
|
MetaOutputInfo **outputs,
|
||||||
|
unsigned int n_outputs)
|
||||||
{
|
{
|
||||||
GVariant *nested_outputs, *properties;
|
|
||||||
guint crtc_id, output_id, transform;
|
|
||||||
int new_mode, x, y;
|
|
||||||
unsigned i;
|
unsigned i;
|
||||||
int screen_width = 0, screen_height = 0;
|
int screen_width = 0, screen_height = 0;
|
||||||
|
|
||||||
while (g_variant_iter_loop (crtcs, "(uiiiu@aua{sv})",
|
for (i = 0; i < n_crtcs; i++)
|
||||||
&crtc_id, &new_mode, &x, &y,
|
|
||||||
&transform, &nested_outputs, NULL))
|
|
||||||
{
|
{
|
||||||
MetaCRTC *crtc = &manager->crtcs[crtc_id];
|
MetaCRTCInfo *crtc_info = crtcs[i];
|
||||||
|
MetaCRTC *crtc = crtc_info->crtc;
|
||||||
crtc->is_dirty = TRUE;
|
crtc->is_dirty = TRUE;
|
||||||
|
|
||||||
if (new_mode == -1)
|
if (crtc_info->mode == NULL)
|
||||||
{
|
{
|
||||||
crtc->rect.x = 0;
|
crtc->rect.x = 0;
|
||||||
crtc->rect.y = 0;
|
crtc->rect.y = 0;
|
||||||
@ -1297,12 +1286,11 @@ apply_config_dummy (MetaMonitorManager *manager,
|
|||||||
MetaMonitorMode *mode;
|
MetaMonitorMode *mode;
|
||||||
MetaOutput *output;
|
MetaOutput *output;
|
||||||
int i, n_outputs;
|
int i, n_outputs;
|
||||||
guint output_id;
|
|
||||||
int width, height;
|
int width, height;
|
||||||
|
|
||||||
mode = &manager->modes[new_mode];
|
mode = crtc_info->mode;
|
||||||
|
|
||||||
if (meta_monitor_transform_is_rotated (transform))
|
if (meta_monitor_transform_is_rotated (crtc_info->transform))
|
||||||
{
|
{
|
||||||
width = mode->height;
|
width = mode->height;
|
||||||
height = mode->width;
|
height = mode->width;
|
||||||
@ -1313,22 +1301,20 @@ apply_config_dummy (MetaMonitorManager *manager,
|
|||||||
height = mode->height;
|
height = mode->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
crtc->rect.x = x;
|
crtc->rect.x = crtc_info->x;
|
||||||
crtc->rect.y = y;
|
crtc->rect.y = crtc_info->y;
|
||||||
crtc->rect.width = width;
|
crtc->rect.width = width;
|
||||||
crtc->rect.height = height;
|
crtc->rect.height = height;
|
||||||
crtc->current_mode = mode;
|
crtc->current_mode = mode;
|
||||||
crtc->transform = transform;
|
crtc->transform = crtc_info->transform;
|
||||||
|
|
||||||
screen_width = MAX (screen_width, x + width);
|
screen_width = MAX (screen_width, crtc_info->x + width);
|
||||||
screen_height = MAX (screen_height, y + height);
|
screen_height = MAX (screen_height, crtc_info->y + height);
|
||||||
|
|
||||||
n_outputs = g_variant_n_children (nested_outputs);
|
n_outputs = crtc_info->outputs->len;
|
||||||
for (i = 0; i < n_outputs; i++)
|
for (i = 0; i < n_outputs; i++)
|
||||||
{
|
{
|
||||||
g_variant_get_child (nested_outputs, i, "u", &output_id);
|
output = ((MetaOutput**)crtc_info->outputs->pdata)[i];
|
||||||
|
|
||||||
output = &manager->outputs[output_id];
|
|
||||||
|
|
||||||
output->is_dirty = TRUE;
|
output->is_dirty = TRUE;
|
||||||
output->crtc = crtc;
|
output->crtc = crtc;
|
||||||
@ -1336,17 +1322,13 @@ apply_config_dummy (MetaMonitorManager *manager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (g_variant_iter_loop (outputs, "(u@a{sv})",
|
for (i = 0; i < n_outputs; i++)
|
||||||
&output_id, &properties))
|
|
||||||
{
|
{
|
||||||
MetaOutput *output = &manager->outputs[output_id];
|
MetaOutputInfo *output_info = outputs[i];
|
||||||
gboolean primary, presentation;
|
MetaOutput *output = output_info->output;
|
||||||
|
|
||||||
if (g_variant_lookup (properties, "primary", "b", &primary))
|
output->is_primary = output_info->is_primary;
|
||||||
output->is_primary = primary;
|
output->is_presentation = output_info->is_presentation;
|
||||||
|
|
||||||
if (g_variant_lookup (properties, "presentation", "b", &presentation))
|
|
||||||
output->is_presentation = presentation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable CRTCs not mentioned in the list */
|
/* Disable CRTCs not mentioned in the list */
|
||||||
@ -1392,18 +1374,15 @@ apply_config_dummy (MetaMonitorManager *manager,
|
|||||||
|
|
||||||
void
|
void
|
||||||
meta_monitor_manager_apply_configuration (MetaMonitorManager *manager,
|
meta_monitor_manager_apply_configuration (MetaMonitorManager *manager,
|
||||||
GVariant *crtcs,
|
MetaCRTCInfo **crtcs,
|
||||||
GVariant *outputs)
|
unsigned int n_crtcs,
|
||||||
|
MetaOutputInfo **outputs,
|
||||||
|
unsigned int n_outputs)
|
||||||
{
|
{
|
||||||
GVariantIter crtc_iter, output_iter;
|
|
||||||
|
|
||||||
g_variant_iter_init (&crtc_iter, crtcs);
|
|
||||||
g_variant_iter_init (&output_iter, outputs);
|
|
||||||
|
|
||||||
if (manager->backend == META_BACKEND_XRANDR)
|
if (manager->backend == META_BACKEND_XRANDR)
|
||||||
apply_config_xrandr (manager, &crtc_iter, &output_iter);
|
apply_config_xrandr (manager, crtcs, n_crtcs, outputs, n_outputs);
|
||||||
else
|
else
|
||||||
apply_config_dummy (manager, &crtc_iter, &output_iter);
|
apply_config_dummy (manager, crtcs, n_crtcs, outputs, n_outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -1426,10 +1405,12 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
{
|
{
|
||||||
MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton);
|
MetaMonitorManager *manager = META_MONITOR_MANAGER (skeleton);
|
||||||
GVariantIter crtc_iter, output_iter, *nested_outputs;
|
GVariantIter crtc_iter, output_iter, *nested_outputs;
|
||||||
|
GVariant *properties;
|
||||||
guint crtc_id;
|
guint crtc_id;
|
||||||
int new_mode, x, y;
|
int new_mode, x, y;
|
||||||
guint transform;
|
guint transform;
|
||||||
guint output_id;
|
guint output_id;
|
||||||
|
GPtrArray *crtc_infos, *output_infos;
|
||||||
|
|
||||||
if (serial != manager->serial)
|
if (serial != manager->serial)
|
||||||
{
|
{
|
||||||
@ -1439,17 +1420,26 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
return TRUE;
|
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 */
|
/* Validate all arguments */
|
||||||
g_variant_iter_init (&crtc_iter, crtcs);
|
g_variant_iter_init (&crtc_iter, crtcs);
|
||||||
while (g_variant_iter_loop (&crtc_iter, "(uiiiuaua{sv})",
|
while (g_variant_iter_loop (&crtc_iter, "(uiiiuaua{sv})",
|
||||||
&crtc_id, &new_mode, &x, &y, &transform,
|
&crtc_id, &new_mode, &x, &y, &transform,
|
||||||
&nested_outputs, NULL))
|
&nested_outputs, NULL))
|
||||||
{
|
{
|
||||||
|
MetaCRTCInfo *crtc_info;
|
||||||
MetaOutput *first_output;
|
MetaOutput *first_output;
|
||||||
MetaCRTC *crtc;
|
MetaCRTC *crtc;
|
||||||
MetaMonitorMode *mode;
|
MetaMonitorMode *mode;
|
||||||
guint output_id;
|
guint output_id;
|
||||||
|
|
||||||
|
crtc_info = g_slice_new (MetaCRTCInfo);
|
||||||
|
crtc_info->outputs = g_ptr_array_new ();
|
||||||
|
|
||||||
if (crtc_id >= manager->n_crtcs)
|
if (crtc_id >= manager->n_crtcs)
|
||||||
{
|
{
|
||||||
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||||
@ -1458,6 +1448,7 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
crtc = &manager->crtcs[crtc_id];
|
crtc = &manager->crtcs[crtc_id];
|
||||||
|
crtc_info->crtc = crtc;
|
||||||
|
|
||||||
if (new_mode != -1 && (new_mode < 0 || (unsigned)new_mode >= manager->n_modes))
|
if (new_mode != -1 && (new_mode < 0 || (unsigned)new_mode >= manager->n_modes))
|
||||||
{
|
{
|
||||||
@ -1467,6 +1458,7 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
mode = new_mode != -1 ? &manager->modes[new_mode] : NULL;
|
mode = new_mode != -1 ? &manager->modes[new_mode] : NULL;
|
||||||
|
crtc_info->mode = mode;
|
||||||
|
|
||||||
if (mode)
|
if (mode)
|
||||||
{
|
{
|
||||||
@ -1494,6 +1486,8 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
crtc_info->x = x;
|
||||||
|
crtc_info->y = y;
|
||||||
|
|
||||||
if (transform < WL_OUTPUT_TRANSFORM_NORMAL ||
|
if (transform < WL_OUTPUT_TRANSFORM_NORMAL ||
|
||||||
transform > WL_OUTPUT_TRANSFORM_FLIPPED_270 ||
|
transform > WL_OUTPUT_TRANSFORM_FLIPPED_270 ||
|
||||||
@ -1504,6 +1498,7 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
"Invalid transform");
|
"Invalid transform");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
crtc_info->transform = transform;
|
||||||
|
|
||||||
first_output = NULL;
|
first_output = NULL;
|
||||||
while (g_variant_iter_loop (nested_outputs, "u", &output_id))
|
while (g_variant_iter_loop (nested_outputs, "u", &output_id))
|
||||||
@ -1526,6 +1521,7 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
"Output cannot be assigned to this CRTC or mode");
|
"Output cannot be assigned to this CRTC or mode");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
g_ptr_array_add (crtc_info->outputs, output);
|
||||||
|
|
||||||
if (first_output)
|
if (first_output)
|
||||||
{
|
{
|
||||||
@ -1551,8 +1547,11 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_variant_iter_init (&output_iter, outputs);
|
g_variant_iter_init (&output_iter, outputs);
|
||||||
while (g_variant_iter_loop (&output_iter, "(ua{sv})", &output_id, NULL))
|
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)
|
if (output_id >= manager->n_outputs)
|
||||||
{
|
{
|
||||||
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||||
@ -1560,6 +1559,15 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
"Invalid output id");
|
"Invalid output id");
|
||||||
return TRUE;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we were in progress of making a persistent change and we see a
|
/* If we were in progress of making a persistent change and we see a
|
||||||
@ -1572,7 +1580,14 @@ meta_monitor_manager_handle_apply_configuration (MetaDBusDisplayConfig *skeleto
|
|||||||
manager->persistent_timeout_id = 0;
|
manager->persistent_timeout_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta_monitor_manager_apply_configuration (manager, crtcs, outputs);
|
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
|
/* Update MetaMonitorConfig data structures immediately so that we
|
||||||
don't revert the change at the next XRandR event, then wait 20
|
don't revert the change at the next XRandR event, then wait 20
|
||||||
@ -1671,6 +1686,23 @@ meta_monitor_manager_get_outputs (MetaMonitorManager *manager,
|
|||||||
return manager->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
|
int
|
||||||
meta_monitor_manager_get_primary_index (MetaMonitorManager *manager)
|
meta_monitor_manager_get_primary_index (MetaMonitorManager *manager)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user