From b7aa6ee176aadd36384e141af6888ac52fed4a60 Mon Sep 17 00:00:00 2001 From: Daniel Playfair Cal Date: Wed, 6 Jan 2021 22:09:27 +1100 Subject: [PATCH] backend/drm: handle changes in the set of fixed output modes In virtualised environments, it is possible for the set of fixed modes advertised by a connected output to change. This happens for example when resizing the window in the host system, which causes the preferred mode in the virtualised display to change to match the new size of the host window. This change keeps the value of `modes` in `struct wlr_output` updated when the available DRM modes change. Also, it adds a signal `available_modes` which notifies compositors when this occurs. --- backend/drm/drm.c | 127 +++++++++++++++++++++------- include/wlr/interfaces/wlr_output.h | 7 ++ include/wlr/types/wlr_output.h | 1 + types/wlr_output.c | 5 ++ 4 files changed, 111 insertions(+), 29 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 1a8c2637d..3ff20b796 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1221,6 +1221,94 @@ static uint32_t get_possible_crtcs(int fd, drmModeRes *res, static void disconnect_drm_connector(struct wlr_drm_connector *conn); +static bool update_modes(drmModeConnector *drm_conn, + struct wlr_drm_connector *wlr_conn) { + bool changes_made = false; + + struct wlr_output_mode *previous_mode, *tmp_mode; + wl_list_for_each_safe(previous_mode, tmp_mode, &wlr_conn->output.modes, link) { + + struct wlr_drm_mode *previous_drm_mode = (struct wlr_drm_mode *) previous_mode; + bool matched = false; + + for (int j = 0; j < drm_conn->count_modes; ++j) { + if (memcmp(&previous_drm_mode->drm_mode, &drm_conn->modes[j], + sizeof(previous_drm_mode->drm_mode)) == 0) { + matched = true; + continue; + } + } + + if (matched) { + continue; + } + + if (!changes_made) { + changes_made = true; + wlr_log(WLR_INFO, "Removed modes:"); + } + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32" %s", + previous_drm_mode->wlr_mode.width, + previous_drm_mode->wlr_mode.height, + previous_drm_mode->wlr_mode.refresh, + previous_drm_mode->wlr_mode.preferred ? "(preferred)" : ""); + + wl_list_remove(&previous_drm_mode->wlr_mode.link); + free(previous_drm_mode); + } + + if (drm_conn->count_modes > wl_list_length(&wlr_conn->output.modes)) { + wlr_log(WLR_INFO, "New modes:"); + + for (int i = 0; i < drm_conn->count_modes; ++i) { + if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { + continue; + } + + struct wlr_output_mode *existing_mode; + bool matched = false; + wl_list_for_each(existing_mode, &wlr_conn->output.modes, link) { + struct wlr_drm_mode *existing_drm_mode = + (struct wlr_drm_mode *) existing_mode; + if (memcmp(&drm_conn->modes[i], &existing_drm_mode->drm_mode, + sizeof(drm_conn->modes[i])) == 0) { + matched = true; + continue; + } + } + + if (matched) { + continue; + } + + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + mode->drm_mode = drm_conn->modes[i]; + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode); + if (mode->drm_mode.type & DRM_MODE_TYPE_PREFERRED) { + mode->wlr_mode.preferred = true; + } + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32" %s", + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh, + mode->wlr_mode.preferred ? "(preferred)" : ""); + + changes_made = true; + wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link); + } + } + + return changes_made; +} + void scan_drm_connectors(struct wlr_drm_backend *drm) { /* * This GPU is not really a modesetting device. @@ -1322,6 +1410,15 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { } } + if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED + || wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + drm_conn->connection == DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "Scanning '%s' for changed modes", wlr_conn->name); + if (update_modes(drm_conn, wlr_conn)) { + wlr_output_update_available_modes(&wlr_conn->output); + } + } + if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED && drm_conn->connection == DRM_MODE_CONNECTED) { wlr_log(WLR_INFO, "'%s' connected", wlr_conn->name); @@ -1354,35 +1451,7 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { output->make, output->model, output->serial, output->name); wlr_output_set_description(output, description); - wlr_log(WLR_INFO, "Detected modes:"); - - for (int i = 0; i < drm_conn->count_modes; ++i) { - struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); - if (!mode) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - continue; - } - - if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { - free(mode); - continue; - } - - mode->drm_mode = drm_conn->modes[i]; - mode->wlr_mode.width = mode->drm_mode.hdisplay; - mode->wlr_mode.height = mode->drm_mode.vdisplay; - mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode); - if (mode->drm_mode.type & DRM_MODE_TYPE_PREFERRED) { - mode->wlr_mode.preferred = true; - } - - wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32" %s", - mode->wlr_mode.width, mode->wlr_mode.height, - mode->wlr_mode.refresh, - mode->wlr_mode.preferred ? "(preferred)" : ""); - - wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link); - } + update_modes(drm_conn, wlr_conn); wlr_conn->possible_crtcs = get_possible_crtcs(drm->fd, res, drm_conn); if (wlr_conn->possible_crtcs == 0) { diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 6d8da2a5f..696c2c77f 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -109,6 +109,13 @@ void wlr_output_update_mode(struct wlr_output *output, */ void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width, int32_t height, int32_t refresh); +/** + * Update the output available modes. + * + * The backend must call this function when the available modes are updated + * to notify compositors about the change. + */ +void wlr_output_update_available_modes(struct wlr_output *output); /** * Update the current output status. * diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 0af2c4549..1d819aee5 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -170,6 +170,7 @@ struct wlr_output { struct wl_signal bind; // wlr_output_event_bind struct wl_signal enable; struct wl_signal mode; + struct wl_signal available_modes; struct wl_signal scale; struct wl_signal transform; struct wl_signal description; diff --git a/types/wlr_output.c b/types/wlr_output.c index 8dbdfdfba..1cb594cb0 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -226,6 +226,10 @@ void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width, wlr_signal_emit_safe(&output->events.mode, output); } +void wlr_output_update_available_modes(struct wlr_output *output) { + wlr_signal_emit_safe(&output->events.available_modes, output); +} + void wlr_output_set_transform(struct wlr_output *output, enum wl_output_transform transform) { if (output->transform == transform) { @@ -343,6 +347,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_signal_init(&output->events.bind); wl_signal_init(&output->events.enable); wl_signal_init(&output->events.mode); + wl_signal_init(&output->events.available_modes); wl_signal_init(&output->events.scale); wl_signal_init(&output->events.transform); wl_signal_init(&output->events.description);