pH/sommelier/sommelier-output.cc

608 lines
23 KiB
C++
Raw Normal View History

// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "xdg-output-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#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 = *(reinterpret_cast<int*>(host->ctx->dpi.data));
// Choose the DPI bucket which is closest to the target DPI which we
// calculated above.
int* dpi;
sl_array_for_each(dpi, &host->ctx->dpi) {
if (abs(*dpi - target_dpi) < abs(adjusted_dpi - target_dpi))
adjusted_dpi = *dpi;
}
*physical_width = dpi_to_physical_mm(adjusted_dpi, *width);
*physical_height = dpi_to_physical_mm(adjusted_dpi, *height);
}
}
void sl_output_get_logical_dimensions(struct sl_host_output* host,
bool rotated,
int32_t* width,
int32_t* height) {
if (rotated) {
// Pass the dimensions as is (it could be rotated)
*width = host->logical_width;
*height = host->logical_height;
} else {
// The transform here indicates how a window image will be
// rotated when composited. The incoming surface from the
// application will NOT have its dimensions rotated.
// For this reason, in order to calculate the scale factors
// for direct scale, we will need the non rotated logical
// dimensions.
switch (host->transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_FLIPPED:
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
*width = host->logical_width;
*height = host->logical_height;
break;
default:
*width = host->logical_height;
*height = host->logical_width;
break;
}
}
}
void sl_output_init_dimensions_direct(struct sl_host_output* host,
int* out_scale,
int* out_physical_width,
int* out_physical_height,
int* out_width,
int* out_height) {
int32_t virtual_width = host->width;
int32_t virtual_height = host->height;
// This requires xdg_output_manager, it is assumed that it will be
// available and we will have an appropriate set of logical dimensions
// for this particular output.
assert(host->ctx->viewporter);
assert(host->ctx->xdg_output_manager);
// The virtual width/height is computed by this function here based
// on the physical width/height
sl_transform_output_dimensions(host->ctx, &virtual_width, &virtual_height);
host->virt_scale_x = static_cast<double>(virtual_width) / host->width;
host->virt_scale_y = static_cast<double>(virtual_height) / host->height;
*out_width = virtual_width;
*out_height = virtual_height;
// Force the scale to 1
//
// This is reported to the guest through the wl_output protocol.
// This value will signal by how much a compositor will upscale
// all buffers by (1 is no scale).
*out_scale = 1;
// The physical dimensions (in mm) are the same, regardless
// of the provided scale factor.
*out_physical_width = host->physical_width;
*out_physical_height = host->physical_height;
// Retrieve the logical dimensions
int32_t logical_width, logical_height;
sl_output_get_logical_dimensions(host, /*rotated=*/false, &logical_width,
&logical_height);
// We want to be able to transform from virtual to XDG logical
// coordinates
// Virt to XDG -> div
// XDG to Virt -> mul
host->xdg_scale_x =
static_cast<double>(virtual_width) / static_cast<double>(logical_width);
host->xdg_scale_y =
static_cast<double>(virtual_height) / static_cast<double>(logical_height);
if (host->internal) {
host->ctx->virt_scale_x = host->virt_scale_x;
host->ctx->virt_scale_y = host->virt_scale_y;
host->ctx->xdg_scale_x = host->xdg_scale_x;
host->ctx->xdg_scale_y = host->xdg_scale_y;
}
}
void sl_output_get_dimensions_original(struct sl_host_output* host,
int* out_scale,
int* out_physical_width,
int* out_physical_height,
int* out_width,
int* out_height) {
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;
}
}
}
*out_scale = scale;
*out_physical_width = physical_width;
*out_physical_height = physical_height;
*out_width = width;
*out_height = height;
}
// Recalculates the virt_x coordinates of outputs when an output is
// add/removed/changed. skip_host is false if the host is being removed
// (as it should not exist in the list already) and true if it's being
// added/changed.
void sl_output_shift_output_x(struct sl_host_output* host, bool skip_host) {
// Outputs are positioned in a line from left to right ordered base on its
// x position.
struct sl_host_output* output;
int next_output_x = 0;
wl_list_for_each(output, &host->ctx->host_outputs, link) {
if (output->virt_x != next_output_x) {
output->virt_x = next_output_x;
// Skipping sending current output's details here if skip host is set
// as they are sent after this method.
if (!skip_host || output != host) {
// scaled_physical_width/height may have not been set yet.
if (!output->scaled_physical_width || !output->scaled_physical_height) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
if (host->ctx->use_direct_scale) {
sl_output_init_dimensions_direct(host, &scale, &physical_width,
&physical_height, &width, &height);
} else {
sl_output_get_dimensions_original(host, &scale, &physical_width,
&physical_height, &width,
&height);
}
host->scaled_physical_width = physical_width;
host->scaled_physical_height = physical_height;
}
wl_output_send_geometry(
output->resource, output->virt_x, output->virt_y,
output->scaled_physical_width, output->scaled_physical_height,
output->subpixel, output->make, output->model, output->transform);
if (wl_resource_get_version(output->resource) >=
WL_OUTPUT_DONE_SINCE_VERSION)
wl_output_send_done(output->resource);
}
}
next_output_x += output->width;
}
}
void sl_output_send_host_output_state(struct sl_host_output* host) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
if (host->ctx->use_direct_scale) {
sl_output_init_dimensions_direct(host, &scale, &physical_width,
&physical_height, &width, &height);
} else {
sl_output_get_dimensions_original(host, &scale, &physical_width,
&physical_height, &width, &height);
}
host->scaled_physical_width = physical_width;
host->scaled_physical_height = physical_height;
// Shift all outputs that are to the right of host to the right if needed.
sl_output_shift_output_x(host, true);
host->virt_y = 0;
wl_output_send_geometry(host->resource, host->virt_x, host->virt_y,
host->scaled_physical_width,
host->scaled_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 =
static_cast<sl_host_output*>(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;
// host_outputs is sorted by x. Delete then re-insert at the correct position.
wl_list_remove(&host->link);
// Insert at the end by default. If insert_at is not set in the loop,
// hosts's x is larger than all the ones in the list currently.
struct wl_list* insert_at = host->ctx->host_outputs.prev;
struct sl_host_output* iter;
wl_list_for_each(iter, &host->ctx->host_outputs, link) {
if (host->x < iter->x) {
// This is the first output whose x cooridinate is to the right of host,
// therefore insert to the left of it.
insert_at = iter->link.prev;
break;
}
}
wl_list_insert(insert_at, &host->link);
}
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 =
static_cast<sl_host_output*>(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 =
static_cast<sl_host_output*>(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 =
static_cast<sl_host_output*>(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 =
static_cast<sl_host_output*>(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 =
static_cast<sl_host_output*>(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 =
static_cast<sl_host_output*>(zaura_output_get_user_data(output));
host->device_scale_factor = device_scale_factor;
}
static void sl_aura_output_insets(void* data,
struct zaura_output* output,
int top,
int left,
int bottom,
int right) {}
static void sl_aura_output_logical_transform(void* data,
struct zaura_output* output,
int transform) {}
static const struct zaura_output_listener sl_aura_output_listener = {
sl_aura_output_scale, sl_aura_output_connection,
sl_aura_output_device_scale_factor, sl_aura_output_insets,
sl_aura_output_logical_transform};
static void sl_destroy_host_output(struct wl_resource* resource) {
struct sl_host_output* host =
static_cast<sl_host_output*>(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);
// Shift all outputs to the right of the deleted output to the left.
sl_output_shift_output_x(host, false);
delete host;
}
static void sl_xdg_output_logical_position(
void* data, struct zxdg_output_v1* zxdg_output_v1, int32_t x, int32_t y) {
struct sl_host_output* host = static_cast<sl_host_output*>(
zxdg_output_v1_get_user_data(zxdg_output_v1));
host->logical_y = y;
host->logical_x = x;
}
static void sl_xdg_output_logical_size(void* data,
struct zxdg_output_v1* zxdg_output_v1,
int32_t width,
int32_t height) {
struct sl_host_output* host = static_cast<sl_host_output*>(
zxdg_output_v1_get_user_data(zxdg_output_v1));
host->logical_width = width;
host->logical_height = height;
host->expecting_logical_size = false;
}
static void sl_xdg_output_done(void* data,
struct zxdg_output_v1* zxdg_output_v1) {}
static void sl_xdg_output_name(void* data,
struct zxdg_output_v1* zxdg_output_v1,
const char* name) {}
static void sl_xdg_output_desc(void* data,
struct zxdg_output_v1* zxdg_output_v1,
const char* desc) {}
static const struct zxdg_output_v1_listener sl_xdg_output_listener = {
sl_xdg_output_logical_position, sl_xdg_output_logical_size,
sl_xdg_output_done, sl_xdg_output_name, sl_xdg_output_desc};
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 = new sl_host_output();
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 = static_cast<wl_output*>(wl_registry_bind(
wl_display_get_registry(ctx->display), output->id, &wl_output_interface,
wl_resource_get_version(host->resource)));
wl_output_add_listener(host->proxy, &sl_output_listener, host);
output->host_output = 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->virt_x = 0;
host->virt_y = 0;
host->logical_x = 0;
host->logical_y = 0;
host->physical_width = 0;
host->physical_height = 0;
host->scaled_physical_width = 0;
host->scaled_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->logical_width = 1024;
host->logical_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;
host->expecting_logical_size = false;
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_add_listener(host->aura_output, &sl_aura_output_listener,
host);
}
if (ctx->xdg_output_manager) {
host->expecting_logical_size = true;
host->zxdg_output = zxdg_output_manager_v1_get_xdg_output(
ctx->xdg_output_manager->internal, host->proxy);
zxdg_output_v1_add_listener(host->zxdg_output, &sl_xdg_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);
}