/* * Copyright (C) 2019 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-output.h" #include "backends/native/meta-kms-connector.h" #include "backends/native/meta-kms-connector-private.h" #include #include "backends/native/meta-kms-crtc.h" #include "backends/native/meta-kms-device-private.h" #include "backends/native/meta-kms-impl-device.h" #include "backends/native/meta-kms-mode-private.h" #include "backends/native/meta-kms-update-private.h" /* CTA-861.3 HDR Static Metadata Extension, Table 3, * Electro-Optical Transfer Function */ typedef enum { HDR_METADATA_EOTF_TRADITIONAL_GAMMA_SDR = 0, HDR_METADATA_EOTF_TRADITIONAL_GAMMA_HDR = 1, HDR_METADATA_EOTF_PERCEPTUAL_QUANTIZER = 2, HDR_METADATA_EOTF_HYBRID_LOG_GAMMA = 3, } HdrMetadataEotf; /* CTA-861.3 HDR Static Metadata Extension, Table 4, * Static_Metadata_Descriptor_ID */ typedef enum { HDR_STATIC_METADATA_TYPE_1 = 0, } HdrStaticMetadataType; typedef struct _MetaKmsConnectorPropTable { MetaKmsProp props[META_KMS_CONNECTOR_N_PROPS]; MetaKmsEnum dpms_enum[META_KMS_CONNECTOR_DPMS_N_PROPS]; MetaKmsEnum underscan_enum[META_KMS_CONNECTOR_UNDERSCAN_N_PROPS]; MetaKmsEnum privacy_screen_sw_enum[META_KMS_CONNECTOR_PRIVACY_SCREEN_N_PROPS]; MetaKmsEnum privacy_screen_hw_enum[META_KMS_CONNECTOR_PRIVACY_SCREEN_N_PROPS]; MetaKmsEnum scaling_mode_enum[META_KMS_CONNECTOR_SCALING_MODE_N_PROPS]; MetaKmsEnum panel_orientation_enum[META_KMS_CONNECTOR_PANEL_ORIENTATION_N_PROPS]; MetaKmsEnum colorspace_enum[META_KMS_CONNECTOR_COLORSPACE_N_PROPS]; } MetaKmsConnectorPropTable; struct _MetaKmsConnector { GObject parent; MetaKmsDevice *device; uint32_t id; uint32_t type; uint32_t type_id; char *name; drmModeConnection connection; MetaKmsConnectorState *current_state; MetaKmsConnectorPropTable prop_table; uint32_t edid_blob_id; uint32_t tile_blob_id; gboolean fd_held; }; G_DEFINE_TYPE (MetaKmsConnector, meta_kms_connector, G_TYPE_OBJECT) typedef enum _MetaKmsPrivacyScreenHwState { META_KMS_PRIVACY_SCREEN_HW_STATE_DISABLED, META_KMS_PRIVACY_SCREEN_HW_STATE_ENABLED, META_KMS_PRIVACY_SCREEN_HW_STATE_DISABLED_LOCKED, META_KMS_PRIVACY_SCREEN_HW_STATE_ENABLED_LOCKED, } MetaKmsPrivacyScreenHwState; MetaKmsDevice * meta_kms_connector_get_device (MetaKmsConnector *connector) { return connector->device; } uint32_t meta_kms_connector_get_prop_id (MetaKmsConnector *connector, MetaKmsConnectorProp prop) { return connector->prop_table.props[prop].prop_id; } const char * meta_kms_connector_get_prop_name (MetaKmsConnector *connector, MetaKmsConnectorProp prop) { return connector->prop_table.props[prop].name; } uint64_t meta_kms_connector_get_prop_drm_value (MetaKmsConnector *connector, MetaKmsConnectorProp property, uint64_t value) { MetaKmsProp *prop = &connector->prop_table.props[property]; return meta_kms_prop_convert_value (prop, value); } uint32_t meta_kms_connector_get_connector_type (MetaKmsConnector *connector) { return connector->type; } uint32_t meta_kms_connector_get_id (MetaKmsConnector *connector) { return connector->id; } const char * meta_kms_connector_get_name (MetaKmsConnector *connector) { return connector->name; } gboolean meta_kms_connector_can_clone (MetaKmsConnector *connector, MetaKmsConnector *other_connector) { MetaKmsConnectorState *state = connector->current_state; MetaKmsConnectorState *other_state = other_connector->current_state; if (state->common_possible_clones == 0 || other_state->common_possible_clones == 0) return FALSE; if (state->encoder_device_idxs != other_state->encoder_device_idxs) return FALSE; return TRUE; } MetaKmsMode * meta_kms_connector_get_preferred_mode (MetaKmsConnector *connector) { const MetaKmsConnectorState *state; GList *l; state = meta_kms_connector_get_current_state (connector); for (l = state->modes; l; l = l->next) { MetaKmsMode *mode = l->data; const drmModeModeInfo *drm_mode; drm_mode = meta_kms_mode_get_drm_mode (mode); if (drm_mode->type & DRM_MODE_TYPE_PREFERRED) return mode; } return NULL; } const MetaKmsConnectorState * meta_kms_connector_get_current_state (MetaKmsConnector *connector) { return connector->current_state; } gboolean meta_kms_connector_is_underscanning_supported (MetaKmsConnector *connector) { uint32_t underscan_prop_id; underscan_prop_id = meta_kms_connector_get_prop_id (connector, META_KMS_CONNECTOR_PROP_UNDERSCAN); return underscan_prop_id != 0; } gboolean meta_kms_connector_is_privacy_screen_supported (MetaKmsConnector *connector) { return meta_kms_connector_get_prop_id (connector, META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_HW_STATE) != 0; } static gboolean has_privacy_screen_software_toggle (MetaKmsConnector *connector) { return meta_kms_connector_get_prop_id (connector, META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_SW_STATE) != 0; } const MetaKmsRange * meta_kms_connector_get_max_bpc (MetaKmsConnector *connector) { const MetaKmsRange *range = NULL; if (connector->current_state && meta_kms_connector_get_prop_id (connector, META_KMS_CONNECTOR_PROP_MAX_BPC)) range = &connector->current_state->max_bpc; return range; } static void sync_fd_held (MetaKmsConnector *connector, MetaKmsImplDevice *impl_device) { gboolean should_hold_fd; should_hold_fd = connector->current_state && connector->current_state->current_crtc_id != 0; if (connector->fd_held == should_hold_fd) return; if (should_hold_fd) meta_kms_impl_device_hold_fd (impl_device); else meta_kms_impl_device_unhold_fd (impl_device); connector->fd_held = should_hold_fd; } gboolean meta_kms_connector_is_color_space_supported (MetaKmsConnector *connector, MetaOutputColorspace color_space) { return !!(connector->current_state->colorspace.supported & (1 << color_space)); } gboolean meta_kms_connector_is_hdr_metadata_supported (MetaKmsConnector *connector) { return connector->current_state->hdr.supported; } static void set_panel_orientation (MetaKmsConnectorState *state, MetaKmsProp *panel_orientation) { MetaMonitorTransform transform; MetaKmsConnectorPanelOrientation orientation = panel_orientation->value; switch (orientation) { case META_KMS_CONNECTOR_PANEL_ORIENTATION_UPSIDE_DOWN: transform = META_MONITOR_TRANSFORM_180; break; case META_KMS_CONNECTOR_PANEL_ORIENTATION_LEFT_SIDE_UP: transform = META_MONITOR_TRANSFORM_90; break; case META_KMS_CONNECTOR_PANEL_ORIENTATION_RIGHT_SIDE_UP: transform = META_MONITOR_TRANSFORM_270; break; default: transform = META_MONITOR_TRANSFORM_NORMAL; break; } state->panel_orientation_transform = transform; } static void set_privacy_screen (MetaKmsConnectorState *state, MetaKmsConnector *connector, MetaKmsProp *hw_state) { MetaKmsConnectorPrivacyScreen privacy_screen = hw_state->value; if (!meta_kms_connector_is_privacy_screen_supported (connector)) return; switch (privacy_screen) { case META_KMS_PRIVACY_SCREEN_HW_STATE_DISABLED: state->privacy_screen_state = META_PRIVACY_SCREEN_DISABLED; break; case META_KMS_PRIVACY_SCREEN_HW_STATE_DISABLED_LOCKED: state->privacy_screen_state = META_PRIVACY_SCREEN_DISABLED; state->privacy_screen_state |= META_PRIVACY_SCREEN_LOCKED; break; case META_KMS_PRIVACY_SCREEN_HW_STATE_ENABLED: state->privacy_screen_state = META_PRIVACY_SCREEN_ENABLED; break; case META_KMS_PRIVACY_SCREEN_HW_STATE_ENABLED_LOCKED: state->privacy_screen_state = META_PRIVACY_SCREEN_ENABLED; state->privacy_screen_state |= META_PRIVACY_SCREEN_LOCKED; break; default: state->privacy_screen_state = META_PRIVACY_SCREEN_DISABLED; g_warning ("Unknown privacy screen state: %u", privacy_screen); } if (!has_privacy_screen_software_toggle (connector)) state->privacy_screen_state |= META_PRIVACY_SCREEN_LOCKED; } static MetaOutputColorspace drm_color_spaces_to_output_color_spaces (uint64_t drm_color_space) { switch (drm_color_space) { case META_KMS_CONNECTOR_COLORSPACE_DEFAULT: return META_OUTPUT_COLORSPACE_DEFAULT; case META_KMS_CONNECTOR_COLORSPACE_BT2020_RGB: return META_OUTPUT_COLORSPACE_BT2020; default: return META_OUTPUT_COLORSPACE_UNKNOWN; } } static uint64_t supported_drm_color_spaces_to_output_color_spaces (uint64_t drm_support) { uint64_t supported = 0; if (drm_support & (1 << META_KMS_CONNECTOR_COLORSPACE_DEFAULT)) supported |= (1 << META_OUTPUT_COLORSPACE_DEFAULT); if (drm_support & (1 << META_KMS_CONNECTOR_COLORSPACE_BT2020_RGB)) supported |= (1 << META_OUTPUT_COLORSPACE_BT2020); return supported; } uint64_t meta_output_color_space_to_drm_color_space (MetaOutputColorspace color_space) { switch (color_space) { case META_OUTPUT_COLORSPACE_BT2020: return META_KMS_CONNECTOR_COLORSPACE_BT2020_RGB; case META_OUTPUT_COLORSPACE_UNKNOWN: case META_OUTPUT_COLORSPACE_DEFAULT: default: return META_KMS_CONNECTOR_COLORSPACE_DEFAULT; } } static void state_set_properties (MetaKmsConnectorState *state, MetaKmsImplDevice *impl_device, MetaKmsConnector *connector, drmModeConnector *drm_connector) { MetaKmsProp *props = connector->prop_table.props; MetaKmsProp *prop; prop = &props[META_KMS_CONNECTOR_PROP_SUGGESTED_X]; if (prop->prop_id) state->suggested_x = prop->value; prop = &props[META_KMS_CONNECTOR_PROP_SUGGESTED_Y]; if (prop->prop_id) state->suggested_y = prop->value; prop = &props[META_KMS_CONNECTOR_PROP_HOTPLUG_MODE_UPDATE]; if (prop->prop_id) state->hotplug_mode_update = prop->value; prop = &props[META_KMS_CONNECTOR_PROP_SCALING_MODE]; if (prop->prop_id) state->has_scaling = TRUE; prop = &props[META_KMS_CONNECTOR_PROP_PANEL_ORIENTATION]; if (prop->prop_id) set_panel_orientation (state, prop); prop = &props[META_KMS_CONNECTOR_PROP_NON_DESKTOP]; if (prop->prop_id) state->non_desktop = prop->value; prop = &props[META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_HW_STATE]; if (prop->prop_id) set_privacy_screen (state, connector, prop); prop = &props[META_KMS_CONNECTOR_PROP_MAX_BPC]; if (prop->prop_id) { state->max_bpc.value = prop->value; state->max_bpc.min_value = prop->range_min; state->max_bpc.max_value = prop->range_max; } prop = &props[META_KMS_CONNECTOR_PROP_COLORSPACE]; if (prop->prop_id) { state->colorspace.value = drm_color_spaces_to_output_color_spaces (prop->value); state->colorspace.supported = supported_drm_color_spaces_to_output_color_spaces (prop->supported_variants); } } static CoglSubpixelOrder drm_subpixel_order_to_cogl_subpixel_order (drmModeSubPixel subpixel) { switch (subpixel) { case DRM_MODE_SUBPIXEL_NONE: return COGL_SUBPIXEL_ORDER_NONE; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: return COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: return COGL_SUBPIXEL_ORDER_HORIZONTAL_BGR; break; case DRM_MODE_SUBPIXEL_VERTICAL_RGB: return COGL_SUBPIXEL_ORDER_VERTICAL_RGB; break; case DRM_MODE_SUBPIXEL_VERTICAL_BGR: return COGL_SUBPIXEL_ORDER_VERTICAL_BGR; break; case DRM_MODE_SUBPIXEL_UNKNOWN: return COGL_SUBPIXEL_ORDER_UNKNOWN; } return COGL_SUBPIXEL_ORDER_UNKNOWN; } static void state_set_edid (MetaKmsConnectorState *state, MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, uint32_t blob_id) { int fd; drmModePropertyBlobPtr edid_blob; GBytes *edid_data; fd = meta_kms_impl_device_get_fd (impl_device); edid_blob = drmModeGetPropertyBlob (fd, blob_id); if (!edid_blob) { g_warning ("Failed to read EDID of connector %s: %s", connector->name, g_strerror (errno)); return; } edid_data = g_bytes_new (edid_blob->data, edid_blob->length); drmModeFreePropertyBlob (edid_blob); state->edid_data = edid_data; } static void state_set_tile_info (MetaKmsConnectorState *state, MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, uint32_t blob_id) { int fd; drmModePropertyBlobPtr tile_blob; state->tile_info = (MetaTileInfo) { 0 }; fd = meta_kms_impl_device_get_fd (impl_device); tile_blob = drmModeGetPropertyBlob (fd, blob_id); if (!tile_blob) { g_warning ("Failed to read TILE of connector %s: %s", connector->name, strerror (errno)); return; } if (tile_blob->length > 0) { if (sscanf ((char *) tile_blob->data, "%d:%d:%d:%d:%d:%d:%d:%d", &state->tile_info.group_id, &state->tile_info.flags, &state->tile_info.max_h_tiles, &state->tile_info.max_v_tiles, &state->tile_info.loc_h_tile, &state->tile_info.loc_v_tile, &state->tile_info.tile_w, &state->tile_info.tile_h) != 8) { g_warning ("Couldn't understand TILE property blob of connector %s", connector->name); state->tile_info = (MetaTileInfo) { 0 }; } } drmModeFreePropertyBlob (tile_blob); } static double decode_u16_chromaticity (uint16_t value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ return MIN (value * 0.00002, 1.0); } static double decode_u16_min_luminance (uint16_t value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ return value * 0.0001; } gboolean set_output_hdr_metadata (struct hdr_output_metadata *drm_metadata, MetaOutputHdrMetadata *metadata) { struct hdr_metadata_infoframe *infoframe; if (drm_metadata->metadata_type != HDR_STATIC_METADATA_TYPE_1) return FALSE; infoframe = &drm_metadata->hdmi_metadata_type1; if (infoframe->metadata_type != HDR_STATIC_METADATA_TYPE_1) return FALSE; switch (infoframe->eotf) { case HDR_METADATA_EOTF_TRADITIONAL_GAMMA_SDR: metadata->eotf = META_OUTPUT_HDR_METADATA_EOTF_TRADITIONAL_GAMMA_SDR; break; case HDR_METADATA_EOTF_TRADITIONAL_GAMMA_HDR: metadata->eotf = META_OUTPUT_HDR_METADATA_EOTF_TRADITIONAL_GAMMA_HDR; break; case HDR_METADATA_EOTF_PERCEPTUAL_QUANTIZER: metadata->eotf = META_OUTPUT_HDR_METADATA_EOTF_PQ; break; case HDR_METADATA_EOTF_HYBRID_LOG_GAMMA: metadata->eotf = META_OUTPUT_HDR_METADATA_EOTF_HLG; break; } /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ metadata->mastering_display_primaries[0].x = decode_u16_chromaticity (infoframe->display_primaries[0].x); metadata->mastering_display_primaries[0].y = decode_u16_chromaticity (infoframe->display_primaries[0].y); metadata->mastering_display_primaries[1].x = decode_u16_chromaticity (infoframe->display_primaries[1].x); metadata->mastering_display_primaries[1].y = decode_u16_chromaticity (infoframe->display_primaries[1].y); metadata->mastering_display_primaries[2].x = decode_u16_chromaticity (infoframe->display_primaries[2].x); metadata->mastering_display_primaries[2].y = decode_u16_chromaticity (infoframe->display_primaries[2].y); metadata->mastering_display_white_point.x = decode_u16_chromaticity (infoframe->white_point.x); metadata->mastering_display_white_point.y = decode_u16_chromaticity (infoframe->white_point.y); metadata->mastering_display_max_luminance = infoframe->max_display_mastering_luminance; metadata->mastering_display_min_luminance = decode_u16_min_luminance (infoframe->min_display_mastering_luminance); metadata->max_cll = infoframe->max_cll; metadata->max_fall = infoframe->max_fall; return TRUE; } static uint16_t encode_u16_chromaticity (double value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ value = MAX (MIN (value, 1.0), 0.0); return round (value / 0.00002); } static uint16_t encode_u16_max_luminance (double value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ return round (MAX (MIN (value, 65535.0), 0.0)); } static uint16_t encode_u16_min_luminance (double value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ value = MAX (MIN (value, 6.5535), 0.0); return round (value / 0.0001); } static uint16_t encode_u16_max_cll (double value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ return round (MAX (MIN (value, 65535.0), 0.0)); } static uint16_t encode_u16_max_fall (double value) { /* CTA-861.3 HDR Static Metadata Extension, 3.2.1 Static Metadata Type 1 */ return round (MAX (MIN (value, 65535.0), 0.0)); } void meta_set_drm_hdr_metadata (MetaOutputHdrMetadata *metadata, struct hdr_output_metadata *drm_metadata) { struct hdr_metadata_infoframe *infoframe = &drm_metadata->hdmi_metadata_type1; drm_metadata->metadata_type = HDR_STATIC_METADATA_TYPE_1; infoframe->metadata_type = HDR_STATIC_METADATA_TYPE_1; switch (metadata->eotf) { case META_OUTPUT_HDR_METADATA_EOTF_TRADITIONAL_GAMMA_SDR: infoframe->eotf = HDR_METADATA_EOTF_TRADITIONAL_GAMMA_SDR; break; case META_OUTPUT_HDR_METADATA_EOTF_TRADITIONAL_GAMMA_HDR: infoframe->eotf = HDR_METADATA_EOTF_TRADITIONAL_GAMMA_HDR; break; case META_OUTPUT_HDR_METADATA_EOTF_PQ: infoframe->eotf = HDR_METADATA_EOTF_PERCEPTUAL_QUANTIZER; break; case META_OUTPUT_HDR_METADATA_EOTF_HLG: infoframe->eotf = HDR_METADATA_EOTF_HYBRID_LOG_GAMMA; break; } infoframe->display_primaries[0].x = encode_u16_chromaticity (metadata->mastering_display_primaries[0].x); infoframe->display_primaries[0].y = encode_u16_chromaticity (metadata->mastering_display_primaries[0].y); infoframe->display_primaries[1].x = encode_u16_chromaticity (metadata->mastering_display_primaries[1].x); infoframe->display_primaries[1].y = encode_u16_chromaticity (metadata->mastering_display_primaries[1].y); infoframe->display_primaries[2].x = encode_u16_chromaticity (metadata->mastering_display_primaries[2].x); infoframe->display_primaries[2].y = encode_u16_chromaticity (metadata->mastering_display_primaries[2].y); infoframe->white_point.x = encode_u16_chromaticity (metadata->mastering_display_white_point.x); infoframe->white_point.y = encode_u16_chromaticity (metadata->mastering_display_white_point.y); infoframe->max_display_mastering_luminance = encode_u16_max_luminance (metadata->mastering_display_max_luminance); infoframe->min_display_mastering_luminance = encode_u16_min_luminance (metadata->mastering_display_min_luminance); infoframe->max_cll = encode_u16_max_cll (metadata->max_cll); infoframe->max_fall = encode_u16_max_fall (metadata->max_fall); } static void state_set_hdr_output_metadata (MetaKmsConnectorState *state, MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, uint32_t blob_id) { int fd; drmModePropertyBlobPtr hdr_blob; MetaOutputHdrMetadata *metadata = &state->hdr.value; struct hdr_output_metadata *drm_metadata; state->hdr.supported = TRUE; state->hdr.unknown = FALSE; metadata->active = TRUE; if (!blob_id) { metadata->active = FALSE; return; } fd = meta_kms_impl_device_get_fd (impl_device); hdr_blob = drmModeGetPropertyBlob (fd, blob_id); if (!hdr_blob) { metadata->active = FALSE; return; } if (hdr_blob->length < sizeof (*drm_metadata)) { g_warning ("HDR_OUTPUT_METADATA smaller than expected for type 1"); state->hdr.unknown = TRUE; goto out; } drm_metadata = hdr_blob->data; if (!set_output_hdr_metadata (drm_metadata, metadata)) { state->hdr.unknown = TRUE; goto out; } out: drmModeFreePropertyBlob (hdr_blob); } static void state_set_blobs (MetaKmsConnectorState *state, MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, drmModeConnector *drm_connector) { MetaKmsProp *props = connector->prop_table.props; MetaKmsProp *prop; prop = &props[META_KMS_CONNECTOR_PROP_EDID]; if (prop->prop_id && prop->value) state_set_edid (state, connector, impl_device, prop->value); prop = &props[META_KMS_CONNECTOR_PROP_TILE]; if (prop->prop_id && prop->value) state_set_tile_info (state, connector, impl_device, prop->value); prop = &props[META_KMS_CONNECTOR_PROP_HDR_OUTPUT_METADATA]; if (prop->prop_id) state_set_hdr_output_metadata (state, connector, impl_device, prop->value); } static void state_set_physical_dimensions (MetaKmsConnectorState *state, drmModeConnector *drm_connector) { state->width_mm = drm_connector->mmWidth; state->height_mm = drm_connector->mmHeight; } static void state_set_modes (MetaKmsConnectorState *state, MetaKmsImplDevice *impl_device, drmModeConnector *drm_connector) { int i; for (i = 0; i < drm_connector->count_modes; i++) { MetaKmsMode *mode; mode = meta_kms_mode_new (impl_device, &drm_connector->modes[i], META_KMS_MODE_FLAG_NONE); state->modes = g_list_prepend (state->modes, mode); } state->modes = g_list_reverse (state->modes); } static void set_encoder_device_idx_bit (uint32_t *encoder_device_idxs, uint32_t encoder_id, MetaKmsImplDevice *impl_device, drmModeRes *drm_resources) { int fd; int i; fd = meta_kms_impl_device_get_fd (impl_device); for (i = 0; i < drm_resources->count_encoders; i++) { drmModeEncoder *drm_encoder; drm_encoder = drmModeGetEncoder (fd, drm_resources->encoders[i]); if (!drm_encoder) continue; if (drm_encoder->encoder_id == encoder_id) { *encoder_device_idxs |= (1 << i); drmModeFreeEncoder (drm_encoder); break; } drmModeFreeEncoder (drm_encoder); } } static void state_set_crtc_state (MetaKmsConnectorState *state, drmModeConnector *drm_connector, MetaKmsImplDevice *impl_device, drmModeRes *drm_resources) { int fd; int i; uint32_t common_possible_crtcs; uint32_t common_possible_clones; uint32_t encoder_device_idxs; fd = meta_kms_impl_device_get_fd (impl_device); common_possible_crtcs = UINT32_MAX; common_possible_clones = UINT32_MAX; encoder_device_idxs = 0; for (i = 0; i < drm_connector->count_encoders; i++) { drmModeEncoder *drm_encoder; drm_encoder = drmModeGetEncoder (fd, drm_connector->encoders[i]); if (!drm_encoder) continue; common_possible_crtcs &= drm_encoder->possible_crtcs; common_possible_clones &= drm_encoder->possible_clones; set_encoder_device_idx_bit (&encoder_device_idxs, drm_encoder->encoder_id, impl_device, drm_resources); if (drm_connector->encoder_id == drm_encoder->encoder_id) state->current_crtc_id = drm_encoder->crtc_id; drmModeFreeEncoder (drm_encoder); } state->common_possible_crtcs = common_possible_crtcs; state->common_possible_clones = common_possible_clones; state->encoder_device_idxs = encoder_device_idxs; } static MetaKmsConnectorState * meta_kms_connector_state_new (void) { MetaKmsConnectorState *state; state = g_new0 (MetaKmsConnectorState, 1); state->suggested_x = -1; state->suggested_y = -1; return state; } static void meta_kms_connector_state_free (MetaKmsConnectorState *state) { g_clear_pointer (&state->edid_data, g_bytes_unref); g_list_free_full (state->modes, (GDestroyNotify) meta_kms_mode_free); g_free (state); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaKmsConnectorState, meta_kms_connector_state_free); static gboolean kms_modes_equal (GList *modes, GList *other_modes) { GList *l; if (g_list_length (modes) != g_list_length (other_modes)) return FALSE; for (l = modes; l; l = l->next) { GList *k; MetaKmsMode *mode = l->data; for (k = other_modes; k; k = k->next) { MetaKmsMode *other_mode = k->data; if (!meta_kms_mode_equal (mode, other_mode)) return FALSE; } } return TRUE; } static gboolean hdr_primaries_equal (double x1, double x2) { return fabs (x1 - x2) < (0.00002 - DBL_EPSILON); } static gboolean hdr_nits_equal (double x1, double x2) { return fabs (x1 - x2) < (1.0 - DBL_EPSILON); } static gboolean hdr_min_luminance_equal (double x1, double x2) { return fabs (x1 - x2) < (0.0001 - DBL_EPSILON); } gboolean hdr_metadata_equal (MetaOutputHdrMetadata *metadata, MetaOutputHdrMetadata *other_metadata) { if (!metadata->active && !other_metadata->active) return TRUE; if (metadata->active != other_metadata->active) return FALSE; if (metadata->eotf != other_metadata->eotf) return FALSE; if (!hdr_primaries_equal (metadata->mastering_display_primaries[0].x, other_metadata->mastering_display_primaries[0].x) || !hdr_primaries_equal (metadata->mastering_display_primaries[0].y, other_metadata->mastering_display_primaries[0].y) || !hdr_primaries_equal (metadata->mastering_display_primaries[1].x, other_metadata->mastering_display_primaries[1].x) || !hdr_primaries_equal (metadata->mastering_display_primaries[1].y, other_metadata->mastering_display_primaries[1].y) || !hdr_primaries_equal (metadata->mastering_display_primaries[2].x, other_metadata->mastering_display_primaries[2].x) || !hdr_primaries_equal (metadata->mastering_display_primaries[2].y, other_metadata->mastering_display_primaries[2].y) || !hdr_primaries_equal (metadata->mastering_display_white_point.x, other_metadata->mastering_display_white_point.x) || !hdr_primaries_equal (metadata->mastering_display_white_point.y, other_metadata->mastering_display_white_point.y)) return FALSE; if (!hdr_nits_equal (metadata->mastering_display_max_luminance, other_metadata->mastering_display_max_luminance)) return FALSE; if (!hdr_min_luminance_equal (metadata->mastering_display_min_luminance, other_metadata->mastering_display_min_luminance)) return FALSE; if (!hdr_nits_equal (metadata->max_cll, other_metadata->max_cll) || !hdr_nits_equal (metadata->max_fall, other_metadata->max_fall)) return FALSE; return TRUE; } static MetaKmsResourceChanges meta_kms_connector_state_changes (MetaKmsConnectorState *state, MetaKmsConnectorState *new_state) { if (state->current_crtc_id != new_state->current_crtc_id) return META_KMS_RESOURCE_CHANGE_FULL; if (state->common_possible_crtcs != new_state->common_possible_crtcs) return META_KMS_RESOURCE_CHANGE_FULL; if (state->common_possible_clones != new_state->common_possible_clones) return META_KMS_RESOURCE_CHANGE_FULL; if (state->encoder_device_idxs != new_state->encoder_device_idxs) return META_KMS_RESOURCE_CHANGE_FULL; if (state->width_mm != new_state->width_mm) return META_KMS_RESOURCE_CHANGE_FULL; if (state->height_mm != new_state->height_mm) return META_KMS_RESOURCE_CHANGE_FULL; if (state->has_scaling != new_state->has_scaling) return META_KMS_RESOURCE_CHANGE_FULL; if (state->non_desktop != new_state->non_desktop) return META_KMS_RESOURCE_CHANGE_FULL; if (state->subpixel_order != new_state->subpixel_order) return META_KMS_RESOURCE_CHANGE_FULL; if (state->suggested_x != new_state->suggested_x) return META_KMS_RESOURCE_CHANGE_FULL; if (state->suggested_y != new_state->suggested_y) return META_KMS_RESOURCE_CHANGE_FULL; if (state->hotplug_mode_update != new_state->hotplug_mode_update) return META_KMS_RESOURCE_CHANGE_FULL; if (state->panel_orientation_transform != new_state->panel_orientation_transform) return META_KMS_RESOURCE_CHANGE_FULL; if (!meta_tile_info_equal (&state->tile_info, &new_state->tile_info)) return META_KMS_RESOURCE_CHANGE_FULL; if ((state->edid_data && !new_state->edid_data) || !state->edid_data || !g_bytes_equal (state->edid_data, new_state->edid_data)) return META_KMS_RESOURCE_CHANGE_FULL; if (!kms_modes_equal (state->modes, new_state->modes)) return META_KMS_RESOURCE_CHANGE_FULL; if (state->max_bpc.value != new_state->max_bpc.value || state->max_bpc.min_value != new_state->max_bpc.min_value || state->max_bpc.max_value != new_state->max_bpc.max_value) return META_KMS_RESOURCE_CHANGE_FULL; if (state->colorspace.value != new_state->colorspace.value || state->colorspace.supported != new_state->colorspace.supported) return META_KMS_RESOURCE_CHANGE_FULL; if (state->hdr.supported != new_state->hdr.supported || state->hdr.unknown != new_state->hdr.unknown || !hdr_metadata_equal (&state->hdr.value, &new_state->hdr.value)) return META_KMS_RESOURCE_CHANGE_FULL; if (state->privacy_screen_state != new_state->privacy_screen_state) return META_KMS_RESOURCE_CHANGE_PRIVACY_SCREEN; return META_KMS_RESOURCE_CHANGE_NONE; } static void meta_kms_connector_update_state_changes (MetaKmsConnector *connector, MetaKmsResourceChanges changes, MetaKmsConnectorState *new_state) { MetaKmsConnectorState *current_state = connector->current_state; g_return_if_fail (changes != META_KMS_RESOURCE_CHANGE_FULL); if (changes & META_KMS_RESOURCE_CHANGE_PRIVACY_SCREEN) current_state->privacy_screen_state = new_state->privacy_screen_state; } static MetaKmsResourceChanges meta_kms_connector_read_state (MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, drmModeConnector *drm_connector, drmModeRes *drm_resources) { g_autoptr (MetaKmsConnectorState) state = NULL; g_autoptr (MetaKmsConnectorState) current_state = NULL; MetaKmsResourceChanges connector_changes; MetaKmsResourceChanges changes; current_state = g_steal_pointer (&connector->current_state); changes = META_KMS_RESOURCE_CHANGE_NONE; meta_kms_impl_device_update_prop_table (impl_device, drm_connector->props, drm_connector->prop_values, drm_connector->count_props, connector->prop_table.props, META_KMS_CONNECTOR_N_PROPS); if (!drm_connector) { if (current_state) changes = META_KMS_RESOURCE_CHANGE_FULL; goto out; } if (drm_connector->connection != DRM_MODE_CONNECTED) { if (drm_connector->connection != connector->connection) { connector->connection = drm_connector->connection; changes |= META_KMS_RESOURCE_CHANGE_FULL; } goto out; } state = meta_kms_connector_state_new (); state_set_blobs (state, connector, impl_device, drm_connector); state_set_properties (state, impl_device, connector, drm_connector); state->subpixel_order = drm_subpixel_order_to_cogl_subpixel_order (drm_connector->subpixel); state_set_physical_dimensions (state, drm_connector); state_set_modes (state, impl_device, drm_connector); state_set_crtc_state (state, drm_connector, impl_device, drm_resources); if (drm_connector->connection != connector->connection) { connector->connection = drm_connector->connection; changes |= META_KMS_RESOURCE_CHANGE_FULL; } if (!current_state) connector_changes = META_KMS_RESOURCE_CHANGE_FULL; else connector_changes = meta_kms_connector_state_changes (current_state, state); changes |= connector_changes; if (!(changes & META_KMS_RESOURCE_CHANGE_FULL)) { meta_kms_connector_update_state_changes (connector, connector_changes, state); connector->current_state = g_steal_pointer (¤t_state); } else { connector->current_state = g_steal_pointer (&state); } out: sync_fd_held (connector, impl_device); return changes; } MetaKmsResourceChanges meta_kms_connector_update_state_in_impl (MetaKmsConnector *connector, drmModeRes *drm_resources, drmModeConnector *drm_connector) { MetaKmsImplDevice *impl_device; MetaKmsResourceChanges changes; impl_device = meta_kms_device_get_impl_device (connector->device); changes = meta_kms_connector_read_state (connector, impl_device, drm_connector, drm_resources); return changes; } void meta_kms_connector_disable_in_impl (MetaKmsConnector *connector) { MetaKmsConnectorState *current_state; current_state = connector->current_state; if (!current_state) return; current_state->current_crtc_id = 0; } MetaKmsResourceChanges meta_kms_connector_predict_state_in_impl (MetaKmsConnector *connector, MetaKmsUpdate *update) { MetaKmsImplDevice *impl_device; MetaKmsConnectorState *current_state; GList *mode_sets; GList *l; MetaKmsResourceChanges changes = META_KMS_RESOURCE_CHANGE_NONE; GList *connector_updates; current_state = connector->current_state; if (!current_state) return META_KMS_RESOURCE_CHANGE_NONE; mode_sets = meta_kms_update_get_mode_sets (update); for (l = mode_sets; l; l = l->next) { MetaKmsModeSet *mode_set = l->data; MetaKmsCrtc *crtc = mode_set->crtc; if (current_state->current_crtc_id == meta_kms_crtc_get_id (crtc)) { if (g_list_find (mode_set->connectors, connector)) break; else current_state->current_crtc_id = 0; } else { if (g_list_find (mode_set->connectors, connector)) { current_state->current_crtc_id = meta_kms_crtc_get_id (crtc); break; } } } connector_updates = meta_kms_update_get_connector_updates (update); for (l = connector_updates; l; l = l->next) { MetaKmsConnectorUpdate *connector_update = l->data; if (connector_update->connector != connector) continue; if (has_privacy_screen_software_toggle (connector) && connector_update->privacy_screen.has_update && !(current_state->privacy_screen_state & META_PRIVACY_SCREEN_LOCKED)) { if (connector_update->privacy_screen.is_enabled) { if (current_state->privacy_screen_state != META_PRIVACY_SCREEN_ENABLED) changes |= META_KMS_RESOURCE_CHANGE_PRIVACY_SCREEN; current_state->privacy_screen_state = META_PRIVACY_SCREEN_ENABLED; } else { if (current_state->privacy_screen_state != META_PRIVACY_SCREEN_DISABLED) changes |= META_KMS_RESOURCE_CHANGE_PRIVACY_SCREEN; current_state->privacy_screen_state = META_PRIVACY_SCREEN_DISABLED; } } if (connector_update->colorspace.has_update) { g_warn_if_fail (meta_kms_connector_is_color_space_supported ( connector, connector_update->colorspace.value)); current_state->colorspace.value = connector_update->colorspace.value; } if (connector_update->hdr.has_update) { g_warn_if_fail (meta_kms_connector_is_hdr_metadata_supported ( connector)); current_state->hdr.value = connector_update->hdr.value; } } impl_device = meta_kms_device_get_impl_device (connector->device); sync_fd_held (connector, impl_device); return changes; } static void init_properties (MetaKmsConnector *connector, MetaKmsImplDevice *impl_device, drmModeConnector *drm_connector) { MetaKmsConnectorPropTable *prop_table = &connector->prop_table; *prop_table = (MetaKmsConnectorPropTable) { .props = { [META_KMS_CONNECTOR_PROP_CRTC_ID] = { .name = "CRTC_ID", .type = DRM_MODE_PROP_OBJECT, }, [META_KMS_CONNECTOR_PROP_DPMS] = { .name = "DPMS", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->dpms_enum, .num_enum_values = META_KMS_CONNECTOR_DPMS_N_PROPS, }, [META_KMS_CONNECTOR_PROP_UNDERSCAN] = { .name = "underscan", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->underscan_enum, .num_enum_values = META_KMS_CONNECTOR_UNDERSCAN_N_PROPS, .default_value = META_KMS_CONNECTOR_UNDERSCAN_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_UNDERSCAN_HBORDER] = { .name = "underscan hborder", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_UNDERSCAN_VBORDER] = { .name = "underscan vborder", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_SW_STATE] = { .name = "privacy-screen sw-state", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->privacy_screen_sw_enum, .num_enum_values = META_KMS_CONNECTOR_PRIVACY_SCREEN_N_PROPS, .default_value = META_KMS_CONNECTOR_PRIVACY_SCREEN_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_PRIVACY_SCREEN_HW_STATE] = { .name = "privacy-screen hw-state", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->privacy_screen_hw_enum, .num_enum_values = META_KMS_CONNECTOR_PRIVACY_SCREEN_N_PROPS, .default_value = META_KMS_CONNECTOR_PRIVACY_SCREEN_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_EDID] = { .name = "EDID", .type = DRM_MODE_PROP_BLOB, }, [META_KMS_CONNECTOR_PROP_TILE] = { .name = "TILE", .type = DRM_MODE_PROP_BLOB, }, [META_KMS_CONNECTOR_PROP_SUGGESTED_X] = { .name = "suggested X", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_SUGGESTED_Y] = { .name = "suggested Y", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_HOTPLUG_MODE_UPDATE] = { .name = "hotplug_mode_update", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_SCALING_MODE] = { .name = "scaling mode", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->scaling_mode_enum, .num_enum_values = META_KMS_CONNECTOR_SCALING_MODE_N_PROPS, .default_value = META_KMS_CONNECTOR_SCALING_MODE_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_PANEL_ORIENTATION] = { .name = "panel orientation", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->panel_orientation_enum, .num_enum_values = META_KMS_CONNECTOR_PANEL_ORIENTATION_N_PROPS, .default_value = META_KMS_CONNECTOR_PANEL_ORIENTATION_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_NON_DESKTOP] = { .name = "non-desktop", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_MAX_BPC] = { .name = "max bpc", .type = DRM_MODE_PROP_RANGE, }, [META_KMS_CONNECTOR_PROP_COLORSPACE] = { .name = "Colorspace", .type = DRM_MODE_PROP_ENUM, .enum_values = prop_table->colorspace_enum, .num_enum_values = META_KMS_CONNECTOR_COLORSPACE_N_PROPS, .default_value = META_KMS_CONNECTOR_COLORSPACE_UNKNOWN, }, [META_KMS_CONNECTOR_PROP_HDR_OUTPUT_METADATA] = { .name = "HDR_OUTPUT_METADATA", .type = DRM_MODE_PROP_BLOB, }, }, .dpms_enum = { [META_KMS_CONNECTOR_DPMS_ON] = { .name = "On", }, [META_KMS_CONNECTOR_DPMS_STANDBY] = { .name = "Standby", }, [META_KMS_CONNECTOR_DPMS_SUSPEND] = { .name = "Suspend", }, [META_KMS_CONNECTOR_DPMS_OFF] = { .name = "Off", }, }, .underscan_enum = { [META_KMS_CONNECTOR_UNDERSCAN_OFF] = { .name = "off", }, [META_KMS_CONNECTOR_UNDERSCAN_ON] = { .name = "on", }, [META_KMS_CONNECTOR_UNDERSCAN_AUTO] = { .name = "auto", }, }, .privacy_screen_sw_enum = { [META_KMS_CONNECTOR_PRIVACY_SCREEN_ENABLED] = { .name = "Enabled", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_DISABLED] = { .name = "Disabled", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_ENABLED_LOCKED] = { .name = "Enabled-locked", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_DISABLED_LOCKED] = { .name = "Disabled-locked", }, }, .privacy_screen_hw_enum = { [META_KMS_CONNECTOR_PRIVACY_SCREEN_ENABLED] = { .name = "Enabled", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_DISABLED] = { .name = "Disabled", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_ENABLED_LOCKED] = { .name = "Enabled-locked", }, [META_KMS_CONNECTOR_PRIVACY_SCREEN_DISABLED_LOCKED] = { .name = "Disabled-locked", }, }, .scaling_mode_enum = { [META_KMS_CONNECTOR_SCALING_MODE_NONE] = { .name = "None", }, [META_KMS_CONNECTOR_SCALING_MODE_FULL] = { .name = "Full", }, [META_KMS_CONNECTOR_SCALING_MODE_CENTER] = { .name = "Center", }, [META_KMS_CONNECTOR_SCALING_MODE_FULL_ASPECT] = { .name = "Full aspect", }, }, .panel_orientation_enum = { [META_KMS_CONNECTOR_PANEL_ORIENTATION_NORMAL] = { .name = "Normal", }, [META_KMS_CONNECTOR_PANEL_ORIENTATION_UPSIDE_DOWN] = { .name = "Upside Down", }, [META_KMS_CONNECTOR_PANEL_ORIENTATION_LEFT_SIDE_UP] = { .name = "Left Side Up", }, [META_KMS_CONNECTOR_PANEL_ORIENTATION_RIGHT_SIDE_UP] = { .name = "Right Side Up", }, }, .colorspace_enum = { [META_KMS_CONNECTOR_COLORSPACE_DEFAULT] = { .name = "Default", }, [META_KMS_CONNECTOR_COLORSPACE_RGB_WIDE_GAMUT_FIXED_POINT] = { .name = "RGB_Wide_Gamut_Fixed_Point", }, [META_KMS_CONNECTOR_COLORSPACE_RGB_WIDE_GAMUT_FLOATING_POINT] = { .name = "RGB_Wide_Gamut_Floating_Point", }, [META_KMS_CONNECTOR_COLORSPACE_RGB_OPRGB] = { .name = "opRGB", }, [META_KMS_CONNECTOR_COLORSPACE_RGB_DCI_P3_RGB_D65] = { .name = "DCI-P3_RGB_D65", }, [META_KMS_CONNECTOR_COLORSPACE_BT2020_RGB] = { .name = "BT2020_RGB", }, [META_KMS_CONNECTOR_COLORSPACE_BT601_YCC] = { .name = "BT601_YCC", }, [META_KMS_CONNECTOR_COLORSPACE_BT709_YCC] = { .name = "BT709_YCC", }, [META_KMS_CONNECTOR_COLORSPACE_XVYCC_601] = { .name = "XVYCC_601", }, [META_KMS_CONNECTOR_COLORSPACE_XVYCC_709] = { .name = "XVYCC_709", }, [META_KMS_CONNECTOR_COLORSPACE_SYCC_601] = { .name = "SYCC_601", }, [META_KMS_CONNECTOR_COLORSPACE_OPYCC_601] = { .name = "opYCC_601", }, [META_KMS_CONNECTOR_COLORSPACE_BT2020_CYCC] = { .name = "BT2020_CYCC", }, [META_KMS_CONNECTOR_COLORSPACE_BT2020_YCC] = { .name = "BT2020_YCC", }, [META_KMS_CONNECTOR_COLORSPACE_SMPTE_170M_YCC] = { .name = "SMPTE_170M_YCC", }, [META_KMS_CONNECTOR_COLORSPACE_DCI_P3_RGB_THEATER] = { .name = "DCI-P3_RGB_Theater", }, }, }; } static char * make_connector_name (drmModeConnector *drm_connector) { static const char * const connector_type_names[] = { "None", "VGA", "DVI-I", "DVI-D", "DVI-A", "Composite", "SVIDEO", "LVDS", "Component", "DIN", "DP", "HDMI", "HDMI-B", "TV", "eDP", "Virtual", "DSI", }; if (drm_connector->connector_type < G_N_ELEMENTS (connector_type_names)) return g_strdup_printf ("%s-%d", connector_type_names[drm_connector->connector_type], drm_connector->connector_type_id); else return g_strdup_printf ("Unknown%d-%d", drm_connector->connector_type, drm_connector->connector_type_id); } gboolean meta_kms_connector_is_same_as (MetaKmsConnector *connector, drmModeConnector *drm_connector) { return (connector->id == drm_connector->connector_id && connector->type == drm_connector->connector_type && connector->type_id == drm_connector->connector_type_id); } MetaKmsConnector * meta_kms_connector_new (MetaKmsImplDevice *impl_device, drmModeConnector *drm_connector, drmModeRes *drm_resources) { MetaKmsConnector *connector; g_assert (drm_connector); connector = g_object_new (META_TYPE_KMS_CONNECTOR, NULL); connector->device = meta_kms_impl_device_get_device (impl_device); connector->id = drm_connector->connector_id; connector->type = drm_connector->connector_type; connector->type_id = drm_connector->connector_type_id; connector->name = make_connector_name (drm_connector); init_properties (connector, impl_device, drm_connector); meta_kms_connector_read_state (connector, impl_device, drm_connector, drm_resources); return connector; } static void meta_kms_connector_finalize (GObject *object) { MetaKmsConnector *connector = META_KMS_CONNECTOR (object); if (connector->fd_held) { MetaKmsImplDevice *impl_device; impl_device = meta_kms_device_get_impl_device (connector->device); meta_kms_impl_device_unhold_fd (impl_device); } g_clear_pointer (&connector->current_state, meta_kms_connector_state_free); g_free (connector->name); G_OBJECT_CLASS (meta_kms_connector_parent_class)->finalize (object); } static void meta_kms_connector_init (MetaKmsConnector *connector) { } static void meta_kms_connector_class_init (MetaKmsConnectorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_kms_connector_finalize; }