pH/sommelier/sommelier-output.c
2019-09-20 18:57:28 -04:00

378 lines
14 KiB
C

// 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
// The ergonomic advice for monitor distance is 50-75cm away, with laptops
// expected to be closer. This magic number is designed to correct that for the
// purpose of calculating a "useful" DPI.
//
// TODO(crbug.com/988325) Fix sommelier's scaling logic s.t. this ratio is
// unnecessary.
#define LAPTOP_TO_DESKTOP_DISTANCE_RATIO (2.0 / 3.0)
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;
}
void sl_output_get_host_output_state(struct sl_host_output* host,
int* scale,
int* physical_width,
int* physical_height,
int* width,
int* height) {
double preferred_scale =
sl_output_aura_scale_factor_to_double(host->preferred_scale);
double current_scale =
sl_output_aura_scale_factor_to_double(host->current_scale);
// "Ideal" means the scale factor you would need in order to make a pixel in
// the buffer map 1:1 with a physical pixel. In the absence of any better
// information, we assume a device whose display density maps faithfully to
// true pixels (i.e. 1.0).
double ideal_scale_factor = 1.0;
double scale_factor = host->scale_factor;
// Use the scale factor we received from aura shell protocol when available.
if (host->ctx->aura_shell) {
double device_scale_factor =
sl_output_aura_scale_factor_to_double(host->device_scale_factor);
ideal_scale_factor = device_scale_factor * preferred_scale;
scale_factor = device_scale_factor * current_scale;
}
// Always use scale=1 and adjust geometry and mode based on ideal
// scale factor for Xwayland client. For other clients, pick an optimal
// scale and adjust geometry and mode based on it.
if (host->ctx->xwayland) {
if (scale)
*scale = 1;
*physical_width = host->physical_width * ideal_scale_factor / scale_factor;
*physical_height =
host->physical_height * ideal_scale_factor / scale_factor;
*width = host->width * host->ctx->scale / scale_factor;
*height = host->height * host->ctx->scale / scale_factor;
// Historically, X applications use DPI to decide their scale (which is not
// ideal). The main problem is that in order to facilitate this, many X
// utilities lie about the DPI of the device in order to achieve the desired
// scaling, e.g. most laptops report a dpi of 96 even if that is inaccurate.
//
// The reason they have to lie is because laptop screens are typically
// closer to your eye than desktop monitors (by a factor of roughly 2/3),
// meaning they have to have proportionally higher DPI in order to "look" as
// high-def as the monitor.
//
// Since sommelier is in the business of lying about the screen's
// dimensions, we will also lie a bit more when we are dealing with the
// internal display, to make its dpi scale like a desktop monitor's would.
if (host->internal) {
*physical_width /= LAPTOP_TO_DESKTOP_DISTANCE_RATIO;
*physical_height /= LAPTOP_TO_DESKTOP_DISTANCE_RATIO;
}
} else {
int s = MIN(ceil(scale_factor / 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 / scale_factor;
*height = host->height * host->ctx->scale * s / scale_factor;
}
if (host->ctx->dpi.size) {
int dpi = (*width * INCH_IN_MM) / *physical_width;
int adjusted_dpi = *((int*)host->ctx->dpi.data);
double mmpd;
int* p;
// Choose the DPI bucket which is closest to the apparent DPI which we
// calculated above.
wl_array_for_each(p, &host->ctx->dpi) {
if (abs(*p - dpi) < abs(adjusted_dpi - dpi))
adjusted_dpi = *p;
}
mmpd = INCH_IN_MM / adjusted_dpi;
*physical_width = *width * mmpd + 0.5;
*physical_height = *height * mmpd + 0.5;
}
}
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);
switch (scale) {
case ZAURA_OUTPUT_SCALE_FACTOR_0400:
case ZAURA_OUTPUT_SCALE_FACTOR_0500:
case ZAURA_OUTPUT_SCALE_FACTOR_0550:
case ZAURA_OUTPUT_SCALE_FACTOR_0600:
case ZAURA_OUTPUT_SCALE_FACTOR_0625:
case ZAURA_OUTPUT_SCALE_FACTOR_0650:
case ZAURA_OUTPUT_SCALE_FACTOR_0700:
case ZAURA_OUTPUT_SCALE_FACTOR_0750:
case ZAURA_OUTPUT_SCALE_FACTOR_0800:
case ZAURA_OUTPUT_SCALE_FACTOR_0850:
case ZAURA_OUTPUT_SCALE_FACTOR_0900:
case ZAURA_OUTPUT_SCALE_FACTOR_0950:
case ZAURA_OUTPUT_SCALE_FACTOR_1000:
case ZAURA_OUTPUT_SCALE_FACTOR_1050:
case ZAURA_OUTPUT_SCALE_FACTOR_1100:
case ZAURA_OUTPUT_SCALE_FACTOR_1150:
case ZAURA_OUTPUT_SCALE_FACTOR_1125:
case ZAURA_OUTPUT_SCALE_FACTOR_1200:
case ZAURA_OUTPUT_SCALE_FACTOR_1250:
case ZAURA_OUTPUT_SCALE_FACTOR_1300:
case ZAURA_OUTPUT_SCALE_FACTOR_1400:
case ZAURA_OUTPUT_SCALE_FACTOR_1450:
case ZAURA_OUTPUT_SCALE_FACTOR_1500:
case ZAURA_OUTPUT_SCALE_FACTOR_1600:
case ZAURA_OUTPUT_SCALE_FACTOR_1750:
case ZAURA_OUTPUT_SCALE_FACTOR_1800:
case ZAURA_OUTPUT_SCALE_FACTOR_2000:
case ZAURA_OUTPUT_SCALE_FACTOR_2200:
case ZAURA_OUTPUT_SCALE_FACTOR_2250:
case ZAURA_OUTPUT_SCALE_FACTOR_2500:
case ZAURA_OUTPUT_SCALE_FACTOR_2750:
case ZAURA_OUTPUT_SCALE_FACTOR_3000:
case ZAURA_OUTPUT_SCALE_FACTOR_3500:
case ZAURA_OUTPUT_SCALE_FACTOR_4000:
case ZAURA_OUTPUT_SCALE_FACTOR_4500:
case ZAURA_OUTPUT_SCALE_FACTOR_5000:
break;
default:
fprintf(stderr, "warning: unknown scale factor: %d\n", scale);
break;
}
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);
}