| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sommelier.h" |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <wayland-client.h> |
| |
| #include "aura-shell-client-protocol.h" |
| |
| #define MAX_OUTPUT_SCALE 2 |
| |
| #define INCH_IN_MM 25.4 |
| |
| // Legacy X11 applications use DPI to decide on their scale. This value is what |
| // the convention for a "normal" scale is. One way to verify the convention is |
| // to note the DPI of a typical monitor circa ~2005, i.e. 20" 1080p. |
| #define DEFACTO_DPI 96 |
| |
| double sl_output_aura_scale_factor_to_double(int scale_factor) { |
| // Aura scale factor is an enum that for all currently know values |
| // is a scale value multipled by 1000. For example, enum value for |
| // 1.25 scale factor is 1250. |
| return scale_factor / 1000.0; |
| } |
| |
| int dpi_to_physical_mm(double dpi, int px) { |
| return px * (INCH_IN_MM / dpi); |
| } |
| |
| void sl_output_get_host_output_state(struct sl_host_output* host, |
| int* scale, |
| int* physical_width, |
| int* physical_height, |
| int* width, |
| int* height) { |
| // The user's chosen zoom level. |
| double current_scale = |
| sl_output_aura_scale_factor_to_double(host->current_scale); |
| |
| // The scale applied to a screen at the default zoom. I.e. this value |
| // determines the meaning of "100%" zoom, and how zoom relates to the |
| // apparent resolution: |
| // |
| // apparent_res = native_res / device_scale_factor * current_scale |
| // |
| // e.g.: On a device with a DSF of 2.0, 80% zoom really means "apply 1.6x |
| // scale", and 50% zoom would give you an apparent resolution equal to the |
| // native one. |
| double device_scale_factor = |
| sl_output_aura_scale_factor_to_double(host->device_scale_factor); |
| |
| // Optimistically, we will try to apply the scale that the user chose. |
| // Failing that, we will use the scale set for this wl_output. |
| double applied_scale = device_scale_factor * current_scale; |
| if (!host->ctx->aura_shell) { |
| applied_scale = host->scale_factor; |
| } |
| |
| int target_dpi = DEFACTO_DPI; |
| if (host->ctx->xwayland) { |
| // For X11, we must fix the scale to be 1 (since X apps typically can't |
| // handle scaling). As a result, we adjust the resolution (based on the |
| // scale we want to apply and sommelier's configuration) and the physical |
| // dimensions (based on what DPI we want the applications to use). E.g.: |
| // - Device scale is 1.25x, with 1920x1080 resolution on a 295mm by 165mm |
| // screen. |
| // - User chosen zoom is 130% |
| // - Sommelier is scaled to 0.5 (a.k.a low density). Since ctx->scale also |
| // has the device scale, it will be 0.625 (i.e. 0.5 * 1.25). |
| // - We want the DPI to be 120 (i.e. 96 * 1.25) |
| // - Meaning 0.21 mm/px |
| // - We report resolution 738x415 (1920x1080 * 0.5 / 1.3) |
| // - We report dimensions 155mm by 87mm (738x415 * 0.21) |
| // This is mostly expected, another way of thinking about them is that zoom |
| // and scale modify the application's understanding of length: |
| // - Increasing the zoom makes lengths appear longer (i.e. fewer mm to work |
| // with over the same real length). |
| // - Scaling the screen does the inverse. |
| if (scale) |
| *scale = 1; |
| *width = host->width * host->ctx->scale / applied_scale; |
| *height = host->height * host->ctx->scale / applied_scale; |
| |
| target_dpi = DEFACTO_DPI * device_scale_factor; |
| *physical_width = dpi_to_physical_mm(target_dpi, *width); |
| *physical_height = dpi_to_physical_mm(target_dpi, *height); |
| } else { |
| // For wayland, we directly apply the scale which combines the user's chosen |
| // preference (from aura) and the scale which this sommelier was configured |
| // for (i.e. based on ctx->scale, which comes from the env/cmd line). |
| // |
| // See above comment: ctx->scale already has the device_scale_factor in it, |
| // so this maths actually looks like: |
| // |
| // applied / ctx->scale |
| // = (current*DSF) / (config*DSF) |
| // = current / config |
| // |
| // E.g. if we configured sommelier to scale everything 0.5x, and the user |
| // has chosen 130% zoom, we are applying 2.6x scale factor. |
| int s = MIN(ceil(applied_scale / host->ctx->scale), MAX_OUTPUT_SCALE); |
| |
| if (scale) |
| *scale = s; |
| *physical_width = host->physical_width; |
| *physical_height = host->physical_height; |
| *width = host->width * host->ctx->scale * s / applied_scale; |
| *height = host->height * host->ctx->scale * s / applied_scale; |
| target_dpi = (*width * INCH_IN_MM) / *physical_width; |
| } |
| |
| if (host->ctx->dpi.size) { |
| int adjusted_dpi = *((int*)host->ctx->dpi.data); |
| int* p; |
| |
| // Choose the DPI bucket which is closest to the target DPI which we |
| // calculated above. |
| wl_array_for_each(p, &host->ctx->dpi) { |
| if (abs(*p - target_dpi) < abs(adjusted_dpi - target_dpi)) |
| adjusted_dpi = *p; |
| } |
| |
| *physical_width = dpi_to_physical_mm(adjusted_dpi, *width); |
| *physical_height = dpi_to_physical_mm(adjusted_dpi, *height); |
| } |
| } |
| |
| void sl_output_send_host_output_state(struct sl_host_output* host) { |
| int scale; |
| int physical_width; |
| int physical_height; |
| int width; |
| int height; |
| |
| sl_output_get_host_output_state(host, &scale, &physical_width, |
| &physical_height, &width, &height); |
| |
| // Use density of internal display for all Xwayland outputs. X11 clients |
| // typically lack support for dynamically changing density so it's |
| // preferred to always use the density of the internal display. |
| if (host->ctx->xwayland) { |
| struct sl_host_output* output; |
| |
| wl_list_for_each(output, &host->ctx->host_outputs, link) { |
| if (output->internal) { |
| int internal_width; |
| int internal_height; |
| |
| sl_output_get_host_output_state(output, NULL, &physical_width, |
| &physical_height, &internal_width, |
| &internal_height); |
| |
| physical_width = (physical_width * width) / internal_width; |
| physical_height = (physical_height * height) / internal_height; |
| break; |
| } |
| } |
| } |
| |
| // X/Y are best left at origin as managed X windows are kept centered on |
| // the root window. The result is that all outputs are overlapping and |
| // pointer events can always be dispatched to the visible region of the |
| // window. |
| wl_output_send_geometry(host->resource, 0, 0, physical_width, physical_height, |
| host->subpixel, host->make, host->model, |
| host->transform); |
| wl_output_send_mode(host->resource, host->flags | WL_OUTPUT_MODE_CURRENT, |
| width, height, host->refresh); |
| if (wl_resource_get_version(host->resource) >= WL_OUTPUT_SCALE_SINCE_VERSION) |
| wl_output_send_scale(host->resource, scale); |
| if (wl_resource_get_version(host->resource) >= WL_OUTPUT_DONE_SINCE_VERSION) |
| wl_output_send_done(host->resource); |
| } |
| |
| static void sl_output_geometry(void* data, |
| struct wl_output* output, |
| int x, |
| int y, |
| int physical_width, |
| int physical_height, |
| int subpixel, |
| const char* make, |
| const char* model, |
| int transform) { |
| struct sl_host_output* host = wl_output_get_user_data(output); |
| |
| host->x = x; |
| host->y = y; |
| host->physical_width = physical_width; |
| host->physical_height = physical_height; |
| host->subpixel = subpixel; |
| free(host->model); |
| host->model = strdup(model); |
| free(host->make); |
| host->make = strdup(make); |
| host->transform = transform; |
| } |
| |
| static void sl_output_mode(void* data, |
| struct wl_output* output, |
| uint32_t flags, |
| int width, |
| int height, |
| int refresh) { |
| struct sl_host_output* host = wl_output_get_user_data(output); |
| |
| host->flags = flags; |
| host->width = width; |
| host->height = height; |
| host->refresh = refresh; |
| } |
| |
| static void sl_output_done(void* data, struct wl_output* output) { |
| struct sl_host_output* host = wl_output_get_user_data(output); |
| |
| // Early out if scale is expected but not yet know. |
| if (host->expecting_scale) |
| return; |
| |
| sl_output_send_host_output_state(host); |
| |
| // Expect scale if aura output exists. |
| if (host->aura_output) |
| host->expecting_scale = 1; |
| } |
| |
| static void sl_output_scale(void* data, |
| struct wl_output* output, |
| int32_t scale_factor) { |
| struct sl_host_output* host = wl_output_get_user_data(output); |
| |
| host->scale_factor = scale_factor; |
| } |
| |
| static const struct wl_output_listener sl_output_listener = { |
| sl_output_geometry, sl_output_mode, sl_output_done, sl_output_scale}; |
| |
| static void sl_aura_output_scale(void* data, |
| struct zaura_output* output, |
| uint32_t flags, |
| uint32_t scale) { |
| struct sl_host_output* host = zaura_output_get_user_data(output); |
| |
| if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT) |
| host->current_scale = scale; |
| if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED) |
| host->preferred_scale = scale; |
| |
| host->expecting_scale = 0; |
| } |
| |
| static void sl_aura_output_connection(void* data, |
| struct zaura_output* output, |
| uint32_t connection) { |
| struct sl_host_output* host = zaura_output_get_user_data(output); |
| |
| host->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL; |
| } |
| |
| static void sl_aura_output_device_scale_factor(void* data, |
| struct zaura_output* output, |
| uint32_t device_scale_factor) { |
| struct sl_host_output* host = zaura_output_get_user_data(output); |
| |
| host->device_scale_factor = device_scale_factor; |
| } |
| |
| static const struct zaura_output_listener sl_aura_output_listener = { |
| sl_aura_output_scale, sl_aura_output_connection, |
| sl_aura_output_device_scale_factor}; |
| |
| static void sl_destroy_host_output(struct wl_resource* resource) { |
| struct sl_host_output* host = wl_resource_get_user_data(resource); |
| |
| if (host->aura_output) |
| zaura_output_destroy(host->aura_output); |
| if (wl_output_get_version(host->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) { |
| wl_output_release(host->proxy); |
| } else { |
| wl_output_destroy(host->proxy); |
| } |
| wl_resource_set_user_data(resource, NULL); |
| wl_list_remove(&host->link); |
| free(host->make); |
| free(host->model); |
| free(host); |
| } |
| |
| static void sl_bind_host_output(struct wl_client* client, |
| void* data, |
| uint32_t version, |
| uint32_t id) { |
| struct sl_output* output = (struct sl_output*)data; |
| struct sl_context* ctx = output->ctx; |
| struct sl_host_output* host; |
| |
| host = malloc(sizeof(*host)); |
| assert(host); |
| host->ctx = ctx; |
| host->resource = wl_resource_create(client, &wl_output_interface, |
| MIN(version, output->version), id); |
| wl_resource_set_implementation(host->resource, NULL, host, |
| sl_destroy_host_output); |
| host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), |
| output->id, &wl_output_interface, |
| wl_resource_get_version(host->resource)); |
| wl_output_set_user_data(host->proxy, host); |
| wl_output_add_listener(host->proxy, &sl_output_listener, host); |
| host->aura_output = NULL; |
| // We assume that first output is internal by default. |
| host->internal = wl_list_empty(&ctx->host_outputs); |
| host->x = 0; |
| host->y = 0; |
| host->physical_width = 0; |
| host->physical_height = 0; |
| host->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; |
| host->make = strdup("unknown"); |
| host->model = strdup("unknown"); |
| host->transform = WL_OUTPUT_TRANSFORM_NORMAL; |
| host->flags = 0; |
| host->width = 1024; |
| host->height = 768; |
| host->refresh = 60000; |
| host->scale_factor = 1; |
| host->current_scale = 1000; |
| host->preferred_scale = 1000; |
| host->device_scale_factor = 1000; |
| host->expecting_scale = 0; |
| wl_list_insert(ctx->host_outputs.prev, &host->link); |
| if (ctx->aura_shell) { |
| host->expecting_scale = 1; |
| host->internal = 0; |
| host->aura_output = |
| zaura_shell_get_aura_output(ctx->aura_shell->internal, host->proxy); |
| zaura_output_set_user_data(host->aura_output, host); |
| zaura_output_add_listener(host->aura_output, &sl_aura_output_listener, |
| host); |
| } |
| } |
| |
| struct sl_global* sl_output_global_create(struct sl_output* output) { |
| return sl_global_create(output->ctx, &wl_output_interface, output->version, |
| output, sl_bind_host_output); |
| } |