diff --git a/src/Makefile.am b/src/Makefile.am index fdccbe682..3dbe731f8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,6 +107,8 @@ libmutter_la_SOURCES = \ backends/meta-monitor-config.h \ backends/meta-monitor-config-manager.c \ backends/meta-monitor-config-manager.h \ + backends/meta-monitor-config-store.c \ + backends/meta-monitor-config-store.h \ backends/meta-monitor.c \ backends/meta-monitor.h \ backends/meta-monitor-private.h \ diff --git a/src/backends/meta-monitor-config-manager.c b/src/backends/meta-monitor-config-manager.c index 07a36362b..4540aaab3 100644 --- a/src/backends/meta-monitor-config-manager.c +++ b/src/backends/meta-monitor-config-manager.c @@ -23,6 +23,7 @@ #include "backends/meta-monitor-config-manager.h" +#include "backends/meta-monitor-config-store.h" #include "backends/meta-monitor-manager-private.h" #include "core/boxes-private.h" @@ -32,6 +33,8 @@ struct _MetaMonitorConfigManager MetaMonitorManager *monitor_manager; + MetaMonitorConfigStore *config_store; + MetaMonitorsConfig *current_config; }; @@ -41,9 +44,6 @@ G_DEFINE_TYPE (MetaMonitorConfigManager, meta_monitor_config_manager, G_DEFINE_TYPE (MetaMonitorsConfig, meta_monitors_config, G_TYPE_OBJECT) -static void -meta_logical_monitor_config_free (MetaLogicalMonitorConfig *logical_monitor_config); - MetaMonitorConfigManager * meta_monitor_config_manager_new (MetaMonitorManager *monitor_manager) { @@ -51,10 +51,18 @@ meta_monitor_config_manager_new (MetaMonitorManager *monitor_manager) config_manager = g_object_new (META_TYPE_MONITOR_CONFIG_MANAGER, NULL); config_manager->monitor_manager = monitor_manager; + config_manager->config_store = g_object_new (META_TYPE_MONITOR_CONFIG_STORE, + NULL); return config_manager; } +MetaMonitorConfigStore * +meta_monitor_config_manager_get_store (MetaMonitorConfigManager *config_manager) +{ + return config_manager->config_store; +} + static gboolean is_crtc_assigned (MetaCrtc *crtc, GPtrArray *crtc_infos) @@ -593,7 +601,7 @@ meta_monitor_config_manager_class_init (MetaMonitorConfigManagerClass *klass) object_class->dispose = meta_monitor_config_manager_dispose; } -static void +void meta_monitor_config_free (MetaMonitorConfig *monitor_config) { meta_monitor_spec_free (monitor_config->monitor_spec); @@ -601,7 +609,7 @@ meta_monitor_config_free (MetaMonitorConfig *monitor_config) g_free (monitor_config); } -static void +void meta_logical_monitor_config_free (MetaLogicalMonitorConfig *logical_monitor_config) { g_list_free_full (logical_monitor_config->monitor_configs, @@ -609,6 +617,94 @@ meta_logical_monitor_config_free (MetaLogicalMonitorConfig *logical_monitor_conf g_free (logical_monitor_config); } +static MetaMonitorsConfigKey * +meta_monitors_config_key_new (GList *logical_monitor_configs) +{ + MetaMonitorsConfigKey *config_key; + GList *monitor_specs; + GList *l; + + monitor_specs = NULL; + for (l = logical_monitor_configs; l; l = l->next) + { + MetaLogicalMonitorConfig *logical_monitor_config = l->data; + GList *k; + + for (k = logical_monitor_config->monitor_configs; k; k = k->next) + { + MetaMonitorConfig *monitor_config = k->data; + MetaMonitorSpec *monitor_spec; + + monitor_spec = meta_monitor_spec_clone (monitor_config->monitor_spec); + monitor_specs = g_list_prepend (monitor_specs, monitor_spec); + } + } + + monitor_specs = g_list_sort (monitor_specs, + (GCompareFunc) meta_monitor_spec_compare); + + config_key = g_new0 (MetaMonitorsConfigKey, 1); + *config_key = (MetaMonitorsConfigKey) { + .monitor_specs = monitor_specs + }; + + return config_key; +} + +void +meta_monitors_config_key_free (MetaMonitorsConfigKey *config_key) +{ + g_list_free_full (config_key->monitor_specs, + (GDestroyNotify) meta_monitor_spec_free); + g_free (config_key); +} + +unsigned int +meta_monitors_config_key_hash (gconstpointer data) +{ + const MetaMonitorsConfigKey *config_key = data; + GList *l; + unsigned long hash; + + hash = 0; + for (l = config_key->monitor_specs; l; l = l->next) + { + MetaMonitorSpec *monitor_spec = l->data; + + hash ^= (g_str_hash (monitor_spec->connector) ^ + g_str_hash (monitor_spec->vendor) ^ + g_str_hash (monitor_spec->product) ^ + g_str_hash (monitor_spec->serial)); + } + + return hash; +} + +gboolean +meta_monitors_config_key_equal (gconstpointer data_a, + gconstpointer data_b) +{ + const MetaMonitorsConfigKey *config_key_a = data_a; + const MetaMonitorsConfigKey *config_key_b = data_b; + GList *l_a, *l_b; + + for (l_a = config_key_a->monitor_specs, l_b = config_key_b->monitor_specs; + l_a && l_b; + l_a = l_a->next, l_b = l_b->next) + { + MetaMonitorSpec *monitor_spec_a = l_a->data; + MetaMonitorSpec *monitor_spec_b = l_b->data; + + if (!meta_monitor_spec_equals (monitor_spec_a, monitor_spec_b)) + return FALSE; + } + + if (l_b || l_b) + return FALSE; + + return TRUE; +} + MetaMonitorsConfig * meta_monitors_config_new (GList *logical_monitor_configs) { @@ -616,6 +712,7 @@ meta_monitors_config_new (GList *logical_monitor_configs) config = g_object_new (META_TYPE_MONITORS_CONFIG, NULL); config->logical_monitor_configs = logical_monitor_configs; + config->key = meta_monitors_config_key_new (logical_monitor_configs); return config; } @@ -625,6 +722,7 @@ meta_monitors_config_finalize (GObject *object) { MetaMonitorsConfig *config = META_MONITORS_CONFIG (object); + meta_monitors_config_key_free (config->key); g_list_free_full (config->logical_monitor_configs, (GDestroyNotify) meta_logical_monitor_config_free); } diff --git a/src/backends/meta-monitor-config-manager.h b/src/backends/meta-monitor-config-manager.h index 974063672..d10cc8fdf 100644 --- a/src/backends/meta-monitor-config-manager.h +++ b/src/backends/meta-monitor-config-manager.h @@ -43,10 +43,16 @@ typedef struct _MetaLogicalMonitorConfig gboolean is_presentation; } MetaLogicalMonitorConfig; +typedef struct _MetaMonitorsConfigKey +{ + GList *monitor_specs; +} MetaMonitorsConfigKey; + struct _MetaMonitorsConfig { GObject parent; + MetaMonitorsConfigKey *key; GList *logical_monitor_configs; }; @@ -56,6 +62,8 @@ G_DECLARE_FINAL_TYPE (MetaMonitorsConfig, meta_monitors_config, MetaMonitorConfigManager * meta_monitor_config_manager_new (MetaMonitorManager *monitor_manager); +MetaMonitorConfigStore * meta_monitor_config_manager_get_store (MetaMonitorConfigManager *config_manager); + gboolean meta_monitor_config_manager_assign (MetaMonitorManager *manager, MetaMonitorsConfig *config, GPtrArray **crtc_infos, @@ -75,4 +83,15 @@ MetaMonitorsConfig * meta_monitor_config_manager_get_current (MetaMonitorConfigM MetaMonitorsConfig * meta_monitors_config_new (GList *logical_monitor_configs); +unsigned int meta_monitors_config_key_hash (gconstpointer config_key); + +gboolean meta_monitors_config_key_equal (gconstpointer config_key_a, + gconstpointer config_key_b); + +void meta_monitors_config_key_free (MetaMonitorsConfigKey *config_key); + +void meta_logical_monitor_config_free (MetaLogicalMonitorConfig *logical_monitor_config); + +void meta_monitor_config_free (MetaMonitorConfig *monitor_config); + #endif /* META_MONITOR_CONFIG_MANAGER_H */ diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c new file mode 100644 index 000000000..eaf70f299 --- /dev/null +++ b/src/backends/meta-monitor-config-store.c @@ -0,0 +1,957 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2017 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include "backends/meta-monitor-config-store.h" + +#include +#include + +#include "backends/meta-monitor-config-manager.h" +#include "core/boxes-private.h" + +/* + * Example configuration: + * + * + * + * + * 0 + * 0 + * + * + * LVDS1 + * Vendor A + * Product A + * Serial A + * + * + * 1920 + * 1080 + * 60.049972534179688 + * + * + * yes + * no + * + * + * 1920 + * 1080 + * + * + * LVDS2 + * Vendor B + * Product B + * Serial B + * + * + * 1920 + * 1080 + * 60.049972534179688 + * + * + * yes + * + * + * + * + */ + +struct _MetaMonitorConfigStore +{ + GObject parent; + + GHashTable *configs; +}; + +typedef enum +{ + STATE_INITIAL, + STATE_MONITORS, + STATE_CONFIGURATION, + STATE_LOGICAL_MONITOR, + STATE_LOGICAL_MONITOR_X, + STATE_LOGICAL_MONITOR_Y, + STATE_LOGICAL_MONITOR_PRIMARY, + STATE_LOGICAL_MONITOR_PRESENTATION, + STATE_MONITOR, + STATE_MONITOR_SPEC, + STATE_MONITOR_SPEC_CONNECTOR, + STATE_MONITOR_SPEC_VENDOR, + STATE_MONITOR_SPEC_PRODUCT, + STATE_MONITOR_SPEC_SERIAL, + STATE_MONITOR_MODE, + STATE_MONITOR_MODE_WIDTH, + STATE_MONITOR_MODE_HEIGHT, + STATE_MONITOR_MODE_RATE, +} ParserState; + +typedef struct +{ + ParserState state; + MetaMonitorConfigStore *config_store; + + GList *current_logical_monitor_configs; + MetaMonitorSpec *current_monitor_spec; + MetaMonitorModeSpec *current_monitor_mode_spec; + MetaMonitorConfig *current_monitor_config; + MetaLogicalMonitorConfig *current_logical_monitor_config; +} ConfigParser; + +G_DEFINE_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + G_TYPE_OBJECT) + +static void +handle_start_element (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_INITIAL: + { + char *version; + + if (!g_str_equal (element_name, "monitors")) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid document element '%s'", element_name); + return; + } + + if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, + error, + G_MARKUP_COLLECT_STRING, "version", &version, + G_MARKUP_COLLECT_INVALID)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing config file format version"); + } + + /* TODO: Handle converting version 1 configuration files. */ + + if (!g_str_equal (version, "2")) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Invalid or unsupported version '%s'", version); + return; + } + + parser->state = STATE_MONITORS; + return; + } + + case STATE_MONITORS: + { + if (!g_str_equal (element_name, "configuration")) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid toplevel element '%s'", element_name); + return; + } + + parser->state = STATE_CONFIGURATION; + return; + } + + case STATE_CONFIGURATION: + { + if (!g_str_equal (element_name, "logicalmonitor")) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid configuration element '%s'", element_name); + return; + } + + parser->current_logical_monitor_config = + g_new0 (MetaLogicalMonitorConfig, 1); + + parser->state = STATE_LOGICAL_MONITOR; + return; + } + + case STATE_LOGICAL_MONITOR: + { + if (g_str_equal (element_name, "x")) + { + parser->state = STATE_LOGICAL_MONITOR_X; + } + else if (g_str_equal (element_name, "y")) + { + parser->state = STATE_LOGICAL_MONITOR_Y; + } + else if (g_str_equal (element_name, "primary")) + { + parser->state = STATE_LOGICAL_MONITOR_PRIMARY; + } + else if (g_str_equal (element_name, "presentation")) + { + parser->state = STATE_LOGICAL_MONITOR_PRESENTATION; + } + else if (g_str_equal (element_name, "monitor")) + { + parser->current_monitor_config = g_new0 (MetaMonitorConfig, 1);; + + parser->state = STATE_MONITOR; + } + else + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid monitor logicalmonitor element '%s'", element_name); + return; + } + + return; + } + + case STATE_LOGICAL_MONITOR_X: + case STATE_LOGICAL_MONITOR_Y: + case STATE_LOGICAL_MONITOR_PRIMARY: + case STATE_LOGICAL_MONITOR_PRESENTATION: + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid logical monitor element '%s'", element_name); + return; + } + + case STATE_MONITOR: + { + if (g_str_equal (element_name, "monitorspec")) + { + parser->current_monitor_spec = g_new0 (MetaMonitorSpec, 1); + + parser->state = STATE_MONITOR_SPEC; + } + else if (g_str_equal (element_name, "mode")) + { + parser->current_monitor_mode_spec = g_new0 (MetaMonitorModeSpec, 1); + + parser->state = STATE_MONITOR_MODE; + } + else + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid monitor element '%s'", element_name); + return; + } + + return; + } + + case STATE_MONITOR_SPEC: + { + if (g_str_equal (element_name, "connector")) + { + parser->state = STATE_MONITOR_SPEC_CONNECTOR; + } + else if (g_str_equal (element_name, "vendor")) + { + parser->state = STATE_MONITOR_SPEC_VENDOR; + } + else if (g_str_equal (element_name, "product")) + { + parser->state = STATE_MONITOR_SPEC_PRODUCT; + } + else if (g_str_equal (element_name, "serial")) + { + parser->state = STATE_MONITOR_SPEC_SERIAL; + } + else + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid monitor spec element '%s'", element_name); + return; + } + + return; + } + + case STATE_MONITOR_SPEC_CONNECTOR: + case STATE_MONITOR_SPEC_VENDOR: + case STATE_MONITOR_SPEC_PRODUCT: + case STATE_MONITOR_SPEC_SERIAL: + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid monitor spec element '%s'", element_name); + return; + } + + case STATE_MONITOR_MODE: + { + if (g_str_equal (element_name, "width")) + { + parser->state = STATE_MONITOR_MODE_WIDTH; + } + else if (g_str_equal (element_name, "height")) + { + parser->state = STATE_MONITOR_MODE_HEIGHT; + } + else if (g_str_equal (element_name, "rate")) + { + parser->state = STATE_MONITOR_MODE_RATE; + } + else + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid mode element '%s'", element_name); + return; + } + + return; + } + + case STATE_MONITOR_MODE_WIDTH: + case STATE_MONITOR_MODE_HEIGHT: + case STATE_MONITOR_MODE_RATE: + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid mode sub element '%s'", element_name); + return; + } + } +} + +static gboolean +verify_monitor_spec (MetaMonitorSpec *monitor_spec, + GError **error) +{ + if (monitor_spec->connector && + monitor_spec->vendor && + monitor_spec->product && + monitor_spec->serial) + { + return TRUE; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Monitor spec incomplete"); + return FALSE; + } +} + +static gboolean +verify_monitor_mode (MetaMonitorModeSpec *monitor_mode_spec, + GError **error) +{ + if (monitor_mode_spec->width > 0 && + monitor_mode_spec->height > 0 && + monitor_mode_spec->refresh_rate > 0.0f) + { + return TRUE; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Monitor mode invalid"); + return FALSE; + } +} + +static gboolean +verify_monitor_config (MetaMonitorConfig *monitor_config, + GError **error) +{ + if (monitor_config->monitor_spec && monitor_config->mode_spec) + { + return TRUE; + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Monitor config incomplete"); + return FALSE; + } +} + +static gboolean +verify_logical_monitor_config (MetaLogicalMonitorConfig *logical_monitor_config, + GError **error) +{ + if (logical_monitor_config->layout.x < 0 || + logical_monitor_config->layout.y < 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid logical monitor position (%d, %d)", + logical_monitor_config->layout.x, + logical_monitor_config->layout.y); + return FALSE; + } + + if (!logical_monitor_config->monitor_configs) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Logical monitor is empty"); + return FALSE; + } + + return TRUE; +} + +static gboolean +derive_logical_monitor_layout (MetaLogicalMonitorConfig *logical_monitor_config, + GError **error) +{ + MetaMonitorConfig *monitor_config; + int mode_width, mode_height; + GList *l; + + monitor_config = logical_monitor_config->monitor_configs->data; + mode_width = monitor_config->mode_spec->width; + mode_height = monitor_config->mode_spec->height; + + for (l = logical_monitor_config->monitor_configs->next; l; l = l->next) + { + monitor_config = l->data; + + if (monitor_config->mode_spec->width != mode_width || + monitor_config->mode_spec->height != mode_height) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Monitors in logical monitor incompatible"); + return FALSE; + } + } + + logical_monitor_config->layout.width = mode_width; + logical_monitor_config->layout.height = mode_height; + + return TRUE; +} + +static gboolean +verify_config (MetaMonitorsConfig *config, + GError **error) +{ + gboolean has_primary; + GList *region; + GList *l; + + if (!config->logical_monitor_configs) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Monitors config incomplete"); + return FALSE; + } + + region = NULL; + has_primary = FALSE; + for (l = config->logical_monitor_configs; l; l = l->next) + { + MetaLogicalMonitorConfig *logical_monitor_config = l->data; + + if (meta_rectangle_overlaps_with_region (region, + &logical_monitor_config->layout)) + { + g_list_free (region); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Logical monitors overlap"); + return FALSE; + } + + if (has_primary && logical_monitor_config->is_primary) + { + g_list_free (region); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Config contains multiple primary logical monitors"); + return FALSE; + } + else if (logical_monitor_config->is_primary) + { + has_primary = TRUE; + } + + region = g_list_prepend (region, &logical_monitor_config->layout); + } + + g_list_free (region); + + if (!has_primary) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Config is missing primary logical"); + return FALSE; + } + + return TRUE; +} + +static void +handle_end_element (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_LOGICAL_MONITOR_X: + case STATE_LOGICAL_MONITOR_Y: + case STATE_LOGICAL_MONITOR_PRIMARY: + case STATE_LOGICAL_MONITOR_PRESENTATION: + { + parser->state = STATE_LOGICAL_MONITOR; + return; + } + + case STATE_MONITOR_SPEC_CONNECTOR: + case STATE_MONITOR_SPEC_VENDOR: + case STATE_MONITOR_SPEC_PRODUCT: + case STATE_MONITOR_SPEC_SERIAL: + { + parser->state = STATE_MONITOR_SPEC; + return; + } + + case STATE_MONITOR_SPEC: + { + g_assert (g_str_equal (element_name, "monitorspec")); + + if (!verify_monitor_spec (parser->current_monitor_spec, error)) + return; + + parser->current_monitor_config->monitor_spec = + parser->current_monitor_spec; + parser->current_monitor_spec = NULL; + + parser->state = STATE_MONITOR; + return; + } + + case STATE_MONITOR_MODE_WIDTH: + case STATE_MONITOR_MODE_HEIGHT: + case STATE_MONITOR_MODE_RATE: + { + parser->state = STATE_MONITOR_MODE; + return; + } + + case STATE_MONITOR_MODE: + { + g_assert (g_str_equal (element_name, "mode")); + + if (!verify_monitor_mode (parser->current_monitor_mode_spec, error)) + return; + + parser->current_monitor_config->mode_spec = + parser->current_monitor_mode_spec; + parser->current_monitor_mode_spec = NULL; + + parser->state = STATE_MONITOR; + return; + } + + case STATE_MONITOR: + { + MetaLogicalMonitorConfig *logical_monitor_config; + + g_assert (g_str_equal (element_name, "monitor")); + + if (!verify_monitor_config (parser->current_monitor_config, error)) + return; + + logical_monitor_config = parser->current_logical_monitor_config; + + logical_monitor_config->monitor_configs = + g_list_append (logical_monitor_config->monitor_configs, + parser->current_monitor_config); + parser->current_monitor_config = NULL; + + parser->state = STATE_LOGICAL_MONITOR; + return; + } + + case STATE_LOGICAL_MONITOR: + { + MetaLogicalMonitorConfig *logical_monitor_config = + parser->current_logical_monitor_config; + + g_assert (g_str_equal (element_name, "logicalmonitor")); + + if (!verify_logical_monitor_config (logical_monitor_config, error)) + return; + + if (!derive_logical_monitor_layout (logical_monitor_config, error)) + return; + + parser->current_logical_monitor_configs = + g_list_append (parser->current_logical_monitor_configs, + logical_monitor_config); + parser->current_logical_monitor_config = NULL; + + parser->state = STATE_CONFIGURATION; + return; + } + + case STATE_CONFIGURATION: + { + MetaMonitorsConfig *config; + + g_assert (g_str_equal (element_name, "configuration")); + + config = + meta_monitors_config_new (parser->current_logical_monitor_configs); + + if (!verify_config (config, error)) + { + g_object_unref (config); + return; + } + + g_hash_table_replace (parser->config_store->configs, + config->key, config); + + parser->current_logical_monitor_configs = NULL; + + parser->state = STATE_MONITORS; + return; + } + + case STATE_MONITORS: + { + g_assert (g_str_equal (element_name, "monitors")); + + parser->state = STATE_INITIAL; + return; + } + + case STATE_INITIAL: + { + g_assert_not_reached (); + } + } +} + +static gboolean +read_int (const char *text, + gsize text_len, + gint *out_value, + GError **error) +{ + char buf[64]; + int64_t value; + char *end; + + strncpy (buf, text, text_len); + buf[MIN (63, text_len)] = 0; + + value = g_ascii_strtoll (buf, &end, 10); + + if (*end || value < 0 || value > G_MAXINT16) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Expected a number, got %s", buf); + return FALSE; + } + else + { + *out_value = value; + return TRUE; + } +} + +static gboolean +read_float (const char *text, + gsize text_len, + float *out_value, + GError **error) +{ + char buf[64]; + float value; + char *end; + + strncpy (buf, text, text_len); + buf[MIN (63, text_len)] = 0; + + value = g_ascii_strtod (buf, &end); + + if (*end) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Expected a number, got %s", buf); + return FALSE; + } + else + { + *out_value = value; + return TRUE; + } +} + +static gboolean +read_bool (const char *text, + gsize text_len, + gboolean *out_value, + GError **error) +{ + if (strncmp (text, "no", text_len) == 0) + { + *out_value = FALSE; + return TRUE; + } + else if (strncmp (text, "yes", text_len) == 0) + { + *out_value = TRUE; + return TRUE; + } + else + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Invalid boolean value '%.*s'", (int) text_len, text); + return FALSE; + } +} + +static gboolean +is_all_whitespace (const char *text, + gsize text_len) +{ + gsize i; + + for (i = 0; i < text_len; i++) + if (!g_ascii_isspace (text[i])) + return FALSE; + + return TRUE; +} + +static void +handle_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ConfigParser *parser = user_data; + + switch (parser->state) + { + case STATE_INITIAL: + case STATE_MONITORS: + case STATE_CONFIGURATION: + case STATE_LOGICAL_MONITOR: + case STATE_MONITOR: + case STATE_MONITOR_SPEC: + case STATE_MONITOR_MODE: + { + if (!is_all_whitespace (text, text_len)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unexpected content at this point"); + return; + } + + case STATE_MONITOR_SPEC_CONNECTOR: + { + parser->current_monitor_spec->connector = g_strndup (text, text_len); + return; + } + + case STATE_MONITOR_SPEC_VENDOR: + { + parser->current_monitor_spec->vendor = g_strndup (text, text_len); + return; + } + + case STATE_MONITOR_SPEC_PRODUCT: + { + parser->current_monitor_spec->product = g_strndup (text, text_len); + return; + } + + case STATE_MONITOR_SPEC_SERIAL: + { + parser->current_monitor_spec->serial = g_strndup (text, text_len); + return; + } + + case STATE_LOGICAL_MONITOR_X: + { + read_int (text, text_len, + &parser->current_logical_monitor_config->layout.x, error); + return; + } + + case STATE_LOGICAL_MONITOR_Y: + { + read_int (text, text_len, + &parser->current_logical_monitor_config->layout.y, error); + return; + } + + case STATE_LOGICAL_MONITOR_PRIMARY: + { + read_bool (text, text_len, + &parser->current_logical_monitor_config->is_primary, + error); + return; + } + + case STATE_LOGICAL_MONITOR_PRESENTATION: + { + read_bool (text, text_len, + &parser->current_logical_monitor_config->is_presentation, + error); + return; + } + + case STATE_MONITOR_MODE_WIDTH: + { + read_int (text, text_len, + &parser->current_monitor_mode_spec->width, + error); + return; + } + + case STATE_MONITOR_MODE_HEIGHT: + { + read_int (text, text_len, + &parser->current_monitor_mode_spec->height, + error); + return; + } + + case STATE_MONITOR_MODE_RATE: + { + read_float (text, text_len, + &parser->current_monitor_mode_spec->refresh_rate, + error); + return; + } + } +} + +static const GMarkupParser config_parser = { + .start_element = handle_start_element, + .end_element = handle_end_element, + .text = handle_text +}; + +static gboolean +read_config_file (MetaMonitorConfigStore *config_store, + GFile *file, + GError **error) +{ + char *buffer; + gsize size; + ConfigParser parser; + GMarkupParseContext *parse_context; + + if (!g_file_load_contents (file, NULL, &buffer, &size, NULL, error)) + return FALSE; + + parser = (ConfigParser) { + .state = STATE_INITIAL, + .config_store = config_store + }; + + parse_context = g_markup_parse_context_new (&config_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT | + G_MARKUP_PREFIX_ERROR_POSITION, + &parser, NULL); + if (!g_markup_parse_context_parse (parse_context, buffer, size, error)) + { + g_list_free_full (parser.current_logical_monitor_configs, + (GDestroyNotify) meta_logical_monitor_config_free); + g_clear_pointer (&parser.current_monitor_spec, + meta_monitor_spec_free); + g_free (parser.current_monitor_mode_spec); + g_clear_pointer (&parser.current_monitor_config, + meta_monitor_config_free); + g_clear_pointer (&parser.current_logical_monitor_config, + meta_logical_monitor_config_free); + return FALSE; + } + + g_markup_parse_context_free (parse_context); + g_free (buffer); + + return TRUE; +} + +MetaMonitorsConfig * +meta_monitor_config_store_lookup (MetaMonitorConfigStore *config_store, + MetaMonitorsConfigKey *key) +{ + return META_MONITORS_CONFIG (g_hash_table_lookup (config_store->configs, + key)); +} + +void +meta_monitor_config_store_add (MetaMonitorConfigStore *config_store, + MetaMonitorsConfig *config) +{ + g_hash_table_insert (config_store->configs, + config->key, g_object_ref (config)); +} + +gboolean +meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, + const char *path, + GError **error) +{ + g_autoptr (GFile) custom_file = NULL; + + g_hash_table_remove_all (config_store->configs); + + custom_file = g_file_new_for_path (path); + + return read_config_file (config_store, custom_file, error); +} + +int +meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store) +{ + return (int) g_hash_table_size (config_store->configs); +} + +static void +meta_monitor_config_store_dispose (GObject *object) +{ + MetaMonitorConfigStore *config_store = META_MONITOR_CONFIG_STORE (object); + + g_clear_pointer (&config_store->configs, g_hash_table_destroy); + + G_OBJECT_CLASS (meta_monitor_config_store_parent_class)->dispose (object); +} + +static void +meta_monitor_config_store_init (MetaMonitorConfigStore *config_store) +{ + config_store->configs = g_hash_table_new_full (meta_monitors_config_key_hash, + meta_monitors_config_key_equal, + NULL, + g_object_unref); +} + +static void +meta_monitor_config_store_class_init (MetaMonitorConfigStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = meta_monitor_config_store_dispose; +} diff --git a/src/backends/meta-monitor-config-store.h b/src/backends/meta-monitor-config-store.h new file mode 100644 index 000000000..3dc94dbfd --- /dev/null +++ b/src/backends/meta-monitor-config-store.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2017 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_MONITOR_CONFIG_STORE_H +#define META_MONITOR_CONFIG_STORE_H + +#include + +#include "backends/meta-monitor-config-manager.h" + +#define META_TYPE_MONITOR_CONFIG_STORE (meta_monitor_config_store_get_type ()) +G_DECLARE_FINAL_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + META, MONITOR_CONFIG_STORE, GObject) + +MetaMonitorsConfig * meta_monitor_config_store_lookup (MetaMonitorConfigStore *config_store, + MetaMonitorsConfigKey *key); + +void meta_monitor_config_store_add (MetaMonitorConfigStore *config_store, + MetaMonitorsConfig *config); + +gboolean meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, + const char *path, + GError **error); + +int meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store); + +#endif /* META_MONITOR_CONFIG_STORE_H */ diff --git a/src/backends/meta-monitor-manager-private.h b/src/backends/meta-monitor-manager-private.h index 0424d0310..1c2bb262d 100644 --- a/src/backends/meta-monitor-manager-private.h +++ b/src/backends/meta-monitor-manager-private.h @@ -50,6 +50,7 @@ typedef struct _MetaMonitorConfig MetaMonitorConfig; typedef struct _MetaMonitorConfigManager MetaMonitorConfigManager; +typedef struct _MetaMonitorConfigStore MetaMonitorConfigStore; typedef struct _MetaMonitorsConfig MetaMonitorsConfig; typedef struct _MetaMonitor MetaMonitor; diff --git a/src/backends/meta-monitor.c b/src/backends/meta-monitor.c index 20e524ffb..8238b43d6 100644 --- a/src/backends/meta-monitor.c +++ b/src/backends/meta-monitor.c @@ -103,6 +103,27 @@ meta_monitor_spec_equals (MetaMonitorSpec *monitor_spec, g_str_equal (monitor_spec->serial, other_monitor_spec->serial)); } +int +meta_monitor_spec_compare (MetaMonitorSpec *monitor_spec_a, + MetaMonitorSpec *monitor_spec_b) +{ + int ret; + + ret = strcmp (monitor_spec_a->connector, monitor_spec_b->connector); + if (ret != 0) + return ret; + + ret = strcmp (monitor_spec_a->vendor, monitor_spec_b->vendor); + if (ret != 0) + return ret; + + ret = strcmp (monitor_spec_a->product, monitor_spec_b->product); + if (ret != 0) + return ret; + + return strcmp (monitor_spec_a->serial, monitor_spec_b->serial); +} + void meta_monitor_spec_free (MetaMonitorSpec *monitor_spec) { diff --git a/src/backends/meta-monitor.h b/src/backends/meta-monitor.h index 8ed956342..d38b76648 100644 --- a/src/backends/meta-monitor.h +++ b/src/backends/meta-monitor.h @@ -154,6 +154,9 @@ MetaMonitorSpec * meta_monitor_spec_clone (MetaMonitorSpec *monitor_id); gboolean meta_monitor_spec_equals (MetaMonitorSpec *monitor_id, MetaMonitorSpec *other_monitor_id); +int meta_monitor_spec_compare (MetaMonitorSpec *monitor_spec_a, + MetaMonitorSpec *monitor_spec_b); + void meta_monitor_spec_free (MetaMonitorSpec *monitor_id); #endif /* META_MONITOR_H */