wine/dlls/winewayland.drv/display.c
Alexandros Frantzis 1a336a259c winewayland.drv: Make access to Wayland output information thread-safe.
Store all incoming Wayland output state as pending and make it current
in a thread-safe way when the "done" event arrives. This enables other
threads, with proper locking, to safely read consistent and complete
Wayland output information.
2023-05-24 22:20:11 +02:00

330 lines
12 KiB
C

/*
* WAYLAND display device functions
*
* Copyright 2020 Alexandros Frantzis for Collabora Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
#include "config.h"
#include "waylanddrv.h"
#include "wine/debug.h"
#include "ntuser.h"
#include <stdlib.h>
WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
static BOOL force_display_devices_refresh;
void wayland_init_display_devices(BOOL force)
{
UINT32 num_path, num_mode;
TRACE("force=%d\n", force);
if (force) force_display_devices_refresh = TRUE;
/* Trigger refresh in win32u */
NtUserGetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_path, &num_mode);
}
struct output_info
{
int x, y;
struct wayland_output_state *output;
};
static int output_info_cmp_primary_x_y(const void *va, const void *vb)
{
const struct output_info *a = va;
const struct output_info *b = vb;
BOOL a_is_primary = a->x == 0 && a->y == 0;
BOOL b_is_primary = b->x == 0 && b->y == 0;
if (a_is_primary && !b_is_primary) return -1;
if (!a_is_primary && b_is_primary) return 1;
if (a->x < b->x) return -1;
if (a->x > b->x) return 1;
if (a->y < b->y) return -1;
if (a->y > b->y) return 1;
return strcmp(a->output->name, b->output->name);
}
static inline BOOL output_info_overlap(struct output_info *a, struct output_info *b)
{
return b->x < a->x + a->output->current_mode->width &&
b->x + b->output->current_mode->width > a->x &&
b->y < a->y + a->output->current_mode->height &&
b->y + b->output->current_mode->height > a->y;
}
/* Map a point to one of the four quadrants of our 2d coordinate space:
* 0: bottom right (x >= 0, y >= 0)
* 1: top right (x >= 0, y < 0)
* 2: bottom left (x < 0, y >= 0)
* 3: top left (x < 0, y < 0) */
static inline int point_to_quadrant(int x, int y)
{
return (x < 0) * 2 + (y < 0);
}
/* Decide which of two outputs to keep stationary in order
* to resolve an overlap. */
static struct output_info *output_info_get_overlap_anchor(struct output_info *a,
struct output_info *b)
{
/* Preferences for the direction of growth in each quadrant, with a
* lower value signifying a higher preference. */
static const int quadrant_prefs[4][4] =
{
{0, 1, 2, 3}, /* quadrant 0 */
{3, 0, 2, 1}, /* quadrant 1 */
{2, 3, 0, 1}, /* quadrant 2 */
{3, 2, 1, 0}, /* quadrant 3 */
};
int qa = point_to_quadrant(a->output->logical_x, a->output->logical_y);
int qb = point_to_quadrant(b->output->logical_x, b->output->logical_y);
/* Direction of growth if a is the anchor. */
int qab = point_to_quadrant(b->output->logical_x - a->output->logical_x,
b->output->logical_y - a->output->logical_y);
/* Direction of growth if b is the anchor. */
int qba = point_to_quadrant(a->output->logical_x - b->output->logical_x,
a->output->logical_y - b->output->logical_y);
/* If the two output origins are in different quadrants, use the output
* in the lower valued quadrant as the anchor (so effectively outputs
* grow/move away from quadrant 0). */
if (qa != qb) return (qa < qb) ? a : b;
/* If the outputs are in the same quadrant, use the preference for the
* direction of growth in that quadrant to select the anchor. Again the
* intended effect is to grow/move outputs away from the origin. */
return (quadrant_prefs[qa][qab] < quadrant_prefs[qa][qba]) ? a : b;
}
static BOOL output_info_array_resolve_overlaps(struct wl_array *output_info_array)
{
struct output_info *a, *b;
BOOL found_overlap = FALSE;
wl_array_for_each(a, output_info_array)
{
wl_array_for_each(b, output_info_array)
{
struct output_info *anchor, *move;
BOOL x_use_end, y_use_end;
double rel_x, rel_y;
/* Break if we reach the same output in the inner loop, so that we
* don't process output pairs twice (since order doesn't matter for
* our algorithm.) */
if (a == b) break;
if (!output_info_overlap(a, b)) continue;
found_overlap = TRUE;
/* Decide which output to move to resolve the overlap. */
anchor = output_info_get_overlap_anchor(a, b);
move = anchor == a ? b : a;
/* Move the selected output on the X axis to resolve the overlap,
* while maintaining the same relative positioning of the outputs as
* the one they have in logical space. Use either the start or end
* of the moved output as the point to maintain the relative
* position of, depending on whether the anchor is before or after
* the moved output on the axis. */
x_use_end = move->output->logical_x < anchor->output->logical_x;
rel_x = (move->output->logical_x - anchor->output->logical_x +
(x_use_end ? move->output->logical_w : 0)) /
(double)anchor->output->logical_w;
move->x = anchor->x + anchor->output->current_mode->width * rel_x -
(x_use_end ? move->output->current_mode->width : 0);
/* Similarly for the Y axis. */
y_use_end = move->output->logical_y < anchor->output->logical_y;
rel_y = (move->output->logical_y - anchor->output->logical_y +
(y_use_end ? move->output->logical_h : 0)) /
(double)anchor->output->logical_h;
move->y = anchor->y + anchor->output->current_mode->height * rel_y -
(y_use_end ? move->output->current_mode->height : 0);
}
}
return found_overlap;
}
static void output_info_array_arrange_physical_coords(struct wl_array *output_info_array)
{
struct output_info *info;
size_t num_outputs = output_info_array->size / sizeof(struct output_info);
int steps = 0;
/* Set the initial physical pixel coordinates. */
wl_array_for_each(info, output_info_array)
{
info->x = info->output->logical_x;
info->y = info->output->logical_y;
}
/* Try to iteratively resolve overlaps, but be defensive and set an upper
* iteration bound to ensure we avoid infinite loops. */
while (output_info_array_resolve_overlaps(output_info_array) &&
++steps < num_outputs)
continue;
/* Now that we have our physical pixel coordinates, sort from physical left
* to right, but ensure the primary output is first. */
qsort(output_info_array->data, num_outputs, sizeof(struct output_info),
output_info_cmp_primary_x_y);
}
static void wayland_add_device_gpu(const struct gdi_device_manager *device_manager,
void *param)
{
static const WCHAR wayland_gpuW[] = {'W','a','y','l','a','n','d','G','P','U',0};
struct gdi_gpu gpu = {0};
lstrcpyW(gpu.name, wayland_gpuW);
TRACE("id=0x%s name=%s\n",
wine_dbgstr_longlong(gpu.id), wine_dbgstr_w(gpu.name));
device_manager->add_gpu(&gpu, param);
}
static void wayland_add_device_adapter(const struct gdi_device_manager *device_manager,
void *param, INT output_id)
{
struct gdi_adapter adapter;
adapter.id = output_id;
adapter.state_flags = DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
if (output_id == 0)
adapter.state_flags |= DISPLAY_DEVICE_PRIMARY_DEVICE;
TRACE("id=0x%s state_flags=0x%x\n",
wine_dbgstr_longlong(adapter.id), (UINT)adapter.state_flags);
device_manager->add_adapter(&adapter, param);
}
static void wayland_add_device_monitor(const struct gdi_device_manager *device_manager,
void *param, struct output_info *output_info)
{
struct gdi_monitor monitor = {0};
SetRect(&monitor.rc_monitor, output_info->x, output_info->y,
output_info->x + output_info->output->current_mode->width,
output_info->y + output_info->output->current_mode->height);
/* We don't have a direct way to get the work area in Wayland. */
monitor.rc_work = monitor.rc_monitor;
monitor.state_flags = DISPLAY_DEVICE_ATTACHED | DISPLAY_DEVICE_ACTIVE;
TRACE("name=%s rc_monitor=rc_work=%s state_flags=0x%x\n",
output_info->output->name, wine_dbgstr_rect(&monitor.rc_monitor),
(UINT)monitor.state_flags);
device_manager->add_monitor(&monitor, param);
}
static void populate_devmode(struct wayland_output_mode *output_mode, DEVMODEW *mode)
{
mode->dmFields = DM_DISPLAYORIENTATION | DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT |
DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY;
mode->dmDisplayOrientation = DMDO_DEFAULT;
mode->dmDisplayFlags = 0;
mode->dmBitsPerPel = 32;
mode->dmPelsWidth = output_mode->width;
mode->dmPelsHeight = output_mode->height;
mode->dmDisplayFrequency = output_mode->refresh / 1000;
}
static void wayland_add_device_modes(const struct gdi_device_manager *device_manager,
void *param, struct output_info *output_info)
{
struct wayland_output_mode *output_mode;
RB_FOR_EACH_ENTRY(output_mode, &output_info->output->modes,
struct wayland_output_mode, entry)
{
DEVMODEW mode = {.dmSize = sizeof(mode)};
BOOL mode_is_current = output_mode == output_info->output->current_mode;
populate_devmode(output_mode, &mode);
if (mode_is_current)
{
mode.dmFields |= DM_POSITION;
mode.dmPosition.x = output_info->x;
mode.dmPosition.y = output_info->y;
}
device_manager->add_mode(&mode, mode_is_current, param);
}
}
/***********************************************************************
* UpdateDisplayDevices (WAYLAND.@)
*/
BOOL WAYLAND_UpdateDisplayDevices(const struct gdi_device_manager *device_manager,
BOOL force, void *param)
{
struct wayland_output *output;
INT output_id = 0;
struct wl_array output_info_array;
struct output_info *output_info;
if (!force && !force_display_devices_refresh) return TRUE;
TRACE("force=%d force_refresh=%d\n", force, force_display_devices_refresh);
force_display_devices_refresh = FALSE;
wl_array_init(&output_info_array);
pthread_mutex_lock(&process_wayland.output_mutex);
wl_list_for_each(output, &process_wayland.output_list, link)
{
if (!output->current.current_mode) continue;
output_info = wl_array_add(&output_info_array, sizeof(*output_info));
if (output_info) output_info->output = &output->current;
else ERR("Failed to allocate space for output_info\n");
}
output_info_array_arrange_physical_coords(&output_info_array);
/* Populate GDI devices. */
wayland_add_device_gpu(device_manager, param);
wl_array_for_each(output_info, &output_info_array)
{
wayland_add_device_adapter(device_manager, param, output_id);
wayland_add_device_monitor(device_manager, param, output_info);
wayland_add_device_modes(device_manager, param, output_info);
output_id++;
}
wl_array_release(&output_info_array);
pthread_mutex_unlock(&process_wayland.output_mutex);
return TRUE;
}