forked from 12to11/12to11
1272 lines
28 KiB
C
1272 lines
28 KiB
C
/* Wayland compositor running on top of an X server.
|
|
|
|
Copyright (C) 2022 to various contributors.
|
|
|
|
This file is part of 12to11.
|
|
|
|
12to11 is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation, either version 3 of the License, or (at your
|
|
option) any later version.
|
|
|
|
12to11 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 General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "compositor.h"
|
|
|
|
typedef struct _Mode Mode;
|
|
typedef struct _Output Output;
|
|
typedef struct _ScaleChangeCallback ScaleChangeCallback;
|
|
|
|
struct _Mode
|
|
{
|
|
int flags;
|
|
unsigned int width, height;
|
|
unsigned long refresh;
|
|
};
|
|
|
|
struct _Output
|
|
{
|
|
/* The output ID of this output. */
|
|
RROutput output;
|
|
|
|
/* List of display modes. */
|
|
XLList *modes;
|
|
|
|
/* The global associated with this output. */
|
|
struct wl_global *global;
|
|
|
|
/* A list of resources associated with this output. */
|
|
XLList *resources;
|
|
|
|
/* The name of the output. */
|
|
char *name;
|
|
|
|
/* Physical height of this output. */
|
|
unsigned int mm_width, mm_height;
|
|
|
|
/* The X and Y position of this output. */
|
|
int x, y;
|
|
|
|
/* The width and height of this output. */
|
|
int width, height;
|
|
|
|
/* The transform and subpixel layout of this output. */
|
|
uint32_t transform, subpixel;
|
|
|
|
/* The scale of this output. */
|
|
int scale;
|
|
};
|
|
|
|
struct _ScaleChangeCallback
|
|
{
|
|
/* The next and last callbacks in this list. */
|
|
ScaleChangeCallback *next, *last;
|
|
|
|
/* Callback to run when the display scale changes. */
|
|
void (*scale_change) (void *, int);
|
|
|
|
/* Data for the callback. */
|
|
void *data;
|
|
};
|
|
|
|
enum
|
|
{
|
|
ModesChanged = 1,
|
|
GeometryChanged = (1 << 2),
|
|
NameChanged = (1 << 3),
|
|
/* N.B. that this isn't currently checked during comparisons,
|
|
since the rest of the code only supports a single global
|
|
scale. */
|
|
ScaleChanged = (1 << 4),
|
|
};
|
|
|
|
/* List of all outputs registered. */
|
|
static XLList *all_outputs;
|
|
|
|
/* List of all scale factor change callbacks. */
|
|
static ScaleChangeCallback scale_callbacks;
|
|
|
|
/* The scale factor currently applied on a global basis. */
|
|
int global_scale_factor;
|
|
|
|
/* The current real scale factor. */
|
|
static int real_scale_factor;
|
|
|
|
/* Function run upon any kind of XRandR notify event. */
|
|
static void (*change_hook) (Time);
|
|
|
|
static Bool
|
|
ApplyEnvironment (const char *name, int *variable)
|
|
{
|
|
char *env;
|
|
|
|
env = getenv (name);
|
|
|
|
if (!env || !atoi (env))
|
|
return False;
|
|
|
|
*variable = atoi (env);
|
|
return True;
|
|
}
|
|
|
|
static void
|
|
FreeSingleOutputResource (void *data)
|
|
{
|
|
struct wl_resource *resource;
|
|
|
|
/* Set this to NULL so HandleResourceDestroy doesn't try to mutate
|
|
the list inside XLListFree. */
|
|
|
|
resource = data;
|
|
wl_resource_set_user_data (resource, NULL);
|
|
wl_resource_destroy (resource);
|
|
}
|
|
|
|
static void
|
|
FreeOutput (Output *output)
|
|
{
|
|
/* Free all resources. */
|
|
XLListFree (output->resources, FreeSingleOutputResource);
|
|
|
|
/* Free all modes. */
|
|
XLListFree (output->modes, XLFree);
|
|
|
|
/* Free the global if it exists. */
|
|
if (output->global)
|
|
wl_global_destroy (output->global);
|
|
|
|
/* Free the output name. */
|
|
XLFree (output->name);
|
|
|
|
/* Finally, free the output itself. */
|
|
XLFree (output);
|
|
}
|
|
|
|
static void
|
|
FreeSingleOutput (void *data)
|
|
{
|
|
FreeOutput (data);
|
|
}
|
|
|
|
static void
|
|
HandleResourceDestroy (struct wl_resource *resource)
|
|
{
|
|
Output *output;
|
|
|
|
output = wl_resource_get_user_data (resource);
|
|
|
|
/* If output still exists, remove this resource. */
|
|
|
|
if (output)
|
|
output->resources = XLListRemove (output->resources,
|
|
resource);
|
|
}
|
|
|
|
static void
|
|
HandleRelease (struct wl_client *client,
|
|
struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy (resource);
|
|
}
|
|
|
|
static const struct wl_output_interface wl_output_impl =
|
|
{
|
|
.release = HandleRelease,
|
|
};
|
|
|
|
static void
|
|
SendGeometry (Output *output, struct wl_resource *resource)
|
|
{
|
|
wl_output_send_geometry (resource, output->x, output->y,
|
|
output->mm_width, output->mm_height,
|
|
output->subpixel,
|
|
ServerVendor (compositor.display),
|
|
output->name, output->transform);
|
|
}
|
|
|
|
static void
|
|
SendScale (Output *output, struct wl_resource *resource)
|
|
{
|
|
wl_output_send_scale (resource, output->scale);
|
|
}
|
|
|
|
static void
|
|
SendMode (Mode *mode, struct wl_resource *resource)
|
|
{
|
|
wl_output_send_mode (resource, mode->flags, mode->width,
|
|
mode->height, mode->refresh);
|
|
}
|
|
|
|
static Bool
|
|
FindOutputId (RROutput *outputs, int noutputs, RROutput id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < noutputs; ++i)
|
|
{
|
|
if (outputs[i] == id)
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
static void
|
|
HandleOutputBound (struct wl_client *client, Output *output,
|
|
struct wl_resource *resource)
|
|
{
|
|
Surface *surface;
|
|
|
|
/* When a new output is bound, see if a surface owned by the client
|
|
is inside. If it is, send the enter event to the surface. */
|
|
|
|
surface = all_surfaces.next;
|
|
|
|
while (surface != &all_surfaces)
|
|
{
|
|
if (client == wl_resource_get_client (surface->resource)
|
|
&& FindOutputId (surface->outputs, surface->n_outputs,
|
|
output->output))
|
|
wl_surface_send_enter (surface->resource, resource);
|
|
|
|
surface = surface->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
HandleBind (struct wl_client *client, void *data,
|
|
uint32_t version, uint32_t id)
|
|
{
|
|
struct wl_resource *resource;
|
|
Output *output;
|
|
XLList *tem;
|
|
|
|
output = data;
|
|
|
|
resource = wl_resource_create (client, &wl_output_interface,
|
|
version, id);
|
|
|
|
if (!resource)
|
|
{
|
|
wl_client_post_no_memory (client);
|
|
return;
|
|
}
|
|
|
|
wl_resource_set_implementation (resource, &wl_output_impl, data,
|
|
HandleResourceDestroy);
|
|
output->resources = XLListPrepend (output->resources, resource);
|
|
|
|
SendGeometry (output, resource);
|
|
SendScale (output, resource);
|
|
|
|
for (tem = output->modes; tem; tem = tem->next)
|
|
SendMode (tem->data, resource);
|
|
|
|
/* Send the output name. I think it is supposed to be unique. */
|
|
|
|
if (wl_resource_get_version (resource) >= 4)
|
|
wl_output_send_name (resource, output->name);
|
|
|
|
if (wl_resource_get_version (resource) >= 2)
|
|
wl_output_send_done (resource);
|
|
|
|
HandleOutputBound (client, output, resource);
|
|
}
|
|
|
|
static Output *
|
|
AddOutput (void)
|
|
{
|
|
Output *output;
|
|
|
|
output = XLCalloc (1, sizeof *output);
|
|
|
|
return output;
|
|
}
|
|
|
|
static Mode *
|
|
AddMode (Output *output)
|
|
{
|
|
Mode *mode;
|
|
|
|
mode = XLCalloc (1, sizeof *mode);
|
|
output->modes = XLListPrepend (output->modes, mode);
|
|
|
|
return mode;
|
|
}
|
|
|
|
static double
|
|
GetModeRefresh (XRRModeInfo *info)
|
|
{
|
|
double rate, vscan;
|
|
|
|
vscan = info->vTotal;
|
|
|
|
if (info->modeFlags & RR_DoubleScan)
|
|
vscan *= 2;
|
|
|
|
if (info->modeFlags & RR_Interlace)
|
|
vscan /= 2;
|
|
|
|
if (info->hTotal && vscan)
|
|
rate = (info->dotClock
|
|
/ (info->hTotal * vscan));
|
|
else
|
|
rate = 0;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static void
|
|
AppendRRMode (Output *output, RRMode id, XRRScreenResources *res,
|
|
XRRCrtcInfo *info, Bool preferred)
|
|
{
|
|
Mode *mode;
|
|
int i;
|
|
|
|
for (i = 0; i < res->nmode; ++i)
|
|
{
|
|
if (res->modes[i].id == id)
|
|
{
|
|
mode = AddMode (output);
|
|
|
|
mode->width = res->modes[i].width;
|
|
mode->height = res->modes[i].height;
|
|
mode->refresh
|
|
= GetModeRefresh (&res->modes[i]) * 1000;
|
|
|
|
if (info->mode == id)
|
|
mode->flags |= WL_OUTPUT_MODE_CURRENT;
|
|
|
|
if (preferred)
|
|
mode->flags |= WL_OUTPUT_MODE_PREFERRED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
ComputeSubpixel (XRROutputInfo *info)
|
|
{
|
|
switch (info->subpixel_order)
|
|
{
|
|
case SubPixelHorizontalRGB:
|
|
return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB;
|
|
|
|
case SubPixelHorizontalBGR:
|
|
return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR;
|
|
|
|
case SubPixelVerticalRGB:
|
|
return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB;
|
|
|
|
case SubPixelVerticalBGR:
|
|
return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR;
|
|
|
|
case SubPixelNone:
|
|
return WL_OUTPUT_SUBPIXEL_NONE;
|
|
|
|
default:
|
|
return WL_OUTPUT_SUBPIXEL_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
ComputeTransform (XRRCrtcInfo *info)
|
|
{
|
|
switch (info->rotation)
|
|
{
|
|
case RR_Rotate_0:
|
|
return WL_OUTPUT_TRANSFORM_NORMAL;
|
|
|
|
case RR_Rotate_90:
|
|
return WL_OUTPUT_TRANSFORM_90;
|
|
|
|
case RR_Rotate_180:
|
|
return WL_OUTPUT_TRANSFORM_180;
|
|
|
|
case RR_Rotate_270:
|
|
return WL_OUTPUT_TRANSFORM_270;
|
|
|
|
case RR_Reflect_X | RR_Rotate_0:
|
|
return WL_OUTPUT_TRANSFORM_FLIPPED;
|
|
|
|
case RR_Reflect_X | RR_Rotate_90:
|
|
return WL_OUTPUT_TRANSFORM_FLIPPED_90;
|
|
|
|
case RR_Reflect_X | RR_Rotate_180:
|
|
return WL_OUTPUT_TRANSFORM_FLIPPED_180;
|
|
|
|
case RR_Reflect_X | RR_Rotate_270:
|
|
return WL_OUTPUT_TRANSFORM_FLIPPED_270;
|
|
|
|
/* How should Y axis reflection be handled here? */
|
|
default:
|
|
return WL_OUTPUT_TRANSFORM_NORMAL;
|
|
}
|
|
}
|
|
|
|
static XLList *
|
|
BuildOutputTree (void)
|
|
{
|
|
XRRScreenResources *resources;
|
|
XRROutputInfo *info;
|
|
XRRCrtcInfo *crtc;
|
|
int i, j;
|
|
Output *output;
|
|
XLList *all_outputs;
|
|
|
|
all_outputs = NULL;
|
|
|
|
resources = XRRGetScreenResources (compositor.display,
|
|
DefaultRootWindow (compositor.display));
|
|
|
|
if (!resources || !resources->noutput)
|
|
{
|
|
if (resources)
|
|
XRRFreeScreenResources (resources);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < resources->noutput; ++i)
|
|
{
|
|
/* Catch errors in case the output disappears. */
|
|
CatchXErrors ();
|
|
info = XRRGetOutputInfo (compositor.display, resources,
|
|
resources->outputs[i]);
|
|
UncatchXErrors (NULL);
|
|
|
|
if (!info)
|
|
continue;
|
|
|
|
if (info->connection != RR_Disconnected
|
|
&& info->crtc != None)
|
|
{
|
|
/* Catch errors in case the CRT controller disappears. */
|
|
CatchXErrors ();
|
|
crtc = XRRGetCrtcInfo (compositor.display, resources,
|
|
info->crtc);
|
|
UncatchXErrors (NULL);
|
|
|
|
if (!crtc)
|
|
{
|
|
XRRFreeOutputInfo (info);
|
|
continue;
|
|
}
|
|
|
|
output = AddOutput ();
|
|
output->output = resources->outputs[i];
|
|
output->scale = global_scale_factor;
|
|
|
|
ApplyEnvironment ("OUTPUT_SCALE", &output->scale);
|
|
|
|
output->mm_width = info->mm_width;
|
|
output->mm_height = info->mm_height;
|
|
output->x = crtc->x;
|
|
output->y = crtc->y;
|
|
output->width = crtc->width;
|
|
output->height = crtc->height;
|
|
output->transform = ComputeTransform (crtc);
|
|
output->subpixel = ComputeSubpixel (info);
|
|
|
|
all_outputs = XLListPrepend (all_outputs, output);
|
|
|
|
output->name = XLStrdup (info->name);
|
|
|
|
for (j = info->nmode - 1; j >= 0; --j)
|
|
{
|
|
if (info->npreferred != j)
|
|
AppendRRMode (output, info->modes[j],
|
|
resources, crtc, False);
|
|
}
|
|
|
|
AppendRRMode (output, info->modes[info->npreferred],
|
|
resources, crtc, True);
|
|
|
|
XRRFreeCrtcInfo (crtc);
|
|
}
|
|
|
|
XRRFreeOutputInfo (info);
|
|
}
|
|
|
|
XRRFreeScreenResources (resources);
|
|
return all_outputs;
|
|
}
|
|
|
|
static Bool
|
|
AreModesIdentical (XLList *first, XLList *second)
|
|
{
|
|
XLList *tem1, *tem2;
|
|
|
|
/* This explicitly also checks the order in which the modes
|
|
appear. */
|
|
for (tem1 = first, tem2 = second; tem1 && tem2;
|
|
tem1 = tem1->next, tem2 = tem2->next)
|
|
{
|
|
if (memcmp (tem1->data, tem2->data, sizeof (Mode)))
|
|
return False;
|
|
}
|
|
|
|
/* If either tem1 or tem2 are not NULL, then one list ended early,
|
|
so the lists are not identical. */
|
|
|
|
return !tem1 && !tem2;
|
|
}
|
|
|
|
static void
|
|
CompareOutputs (Output *output, Output *other, int *flags)
|
|
{
|
|
int difference;
|
|
|
|
difference = 0;
|
|
|
|
if (!AreModesIdentical (output->modes, other->modes))
|
|
difference |= ModesChanged;
|
|
|
|
if (output->mm_width != other->mm_width
|
|
|| output->mm_height != other->mm_height
|
|
|| output->x != other->x
|
|
|| output->y != other->y
|
|
|| output->subpixel != other->subpixel
|
|
|| output->transform != other->transform)
|
|
difference |= GeometryChanged;
|
|
|
|
if (strcmp (output->name, other->name))
|
|
difference |= NameChanged;
|
|
|
|
*flags = difference;
|
|
}
|
|
|
|
static Output *
|
|
FindOutputById (RROutput output)
|
|
{
|
|
XLList *tem;
|
|
Output *item;
|
|
|
|
for (tem = all_outputs; tem; tem = tem->next)
|
|
{
|
|
item = tem->data;
|
|
|
|
if (item->output == output)
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
MakeGlobal (Output *output)
|
|
{
|
|
XLAssert (!output->global);
|
|
|
|
output->global = wl_global_create (compositor.wl_display,
|
|
&wl_output_interface, 4,
|
|
output, HandleBind);
|
|
|
|
if (!output->global)
|
|
{
|
|
fprintf (stderr, "Failed to allocate global output\n");
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
MakeGlobalsForOutputTree (XLList *list)
|
|
{
|
|
for (; list; list = list->next)
|
|
MakeGlobal (list->data);
|
|
}
|
|
|
|
static void
|
|
SendUpdates (Output *output, int difference)
|
|
{
|
|
XLList *tem, *tem1;
|
|
|
|
if (!difference)
|
|
return;
|
|
|
|
/* Given a mask of differences, send the updated output information
|
|
to all clients. */
|
|
|
|
for (tem = output->resources; tem; tem = tem->next)
|
|
{
|
|
if (difference & GeometryChanged)
|
|
SendGeometry (output, tem->data);
|
|
|
|
if (difference & NameChanged
|
|
&& wl_resource_get_version (tem->data) >= 3)
|
|
wl_output_send_name (tem->data, output->name);
|
|
|
|
if (difference & ModesChanged)
|
|
{
|
|
for (tem1 = output->modes; tem1; tem1 = tem->next)
|
|
SendMode (tem1->data, tem->data);
|
|
}
|
|
|
|
if (difference & ScaleChanged)
|
|
SendScale (output, tem->data);
|
|
|
|
if (wl_resource_get_version (tem->data) >= 2)
|
|
wl_output_send_done (tem->data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
UpdateResourceUserData (Output *output)
|
|
{
|
|
XLList *tem;
|
|
|
|
for (tem = output->resources; tem; tem = tem->next)
|
|
wl_resource_set_user_data (tem->data, output);
|
|
}
|
|
|
|
static void
|
|
NoticeOutputsMaybeChanged (void)
|
|
{
|
|
XLList *new_list, *tem;
|
|
Output *new, *current;
|
|
int change_flags;
|
|
Bool any_change;
|
|
Surface *surface;
|
|
|
|
new_list = BuildOutputTree ();
|
|
any_change = False;
|
|
|
|
/* Since it's hard and race-prone to figure out what changed from
|
|
the RandR notification events themselves, simply compare the
|
|
outputs before and after. */
|
|
|
|
for (tem = new_list; tem; tem = tem->next)
|
|
{
|
|
new = tem->data;
|
|
current = FindOutputById (new->output);
|
|
|
|
if (!current)
|
|
{
|
|
/* This is an entirely new output. Create a new global. */
|
|
MakeGlobal (new);
|
|
|
|
/* Mark the list of outputs as having changed. */
|
|
any_change = True;
|
|
continue;
|
|
}
|
|
|
|
/* Otherwise, this output already exists. Tell clients about
|
|
any changes that happened, and transfer the existing global
|
|
and resources to the new output. */
|
|
new->global = current->global;
|
|
new->resources = current->resources;
|
|
|
|
/* Update the user data of the globals and resources. */
|
|
wl_global_set_user_data (new->global, new);
|
|
UpdateResourceUserData (new);
|
|
|
|
/* Clear the current output's globals. Otherwise, they will get
|
|
freed later on. */
|
|
current->global = NULL;
|
|
current->resources = NULL;
|
|
|
|
/* Compare the two outputs to determine what updates must be
|
|
made. */
|
|
CompareOutputs (new, current, &change_flags);
|
|
|
|
/* Send updates. */
|
|
SendUpdates (new, change_flags);
|
|
|
|
/* Mark the list of outputs as having changed if some attribute
|
|
of the output did change. */
|
|
if (change_flags)
|
|
any_change = True;
|
|
}
|
|
|
|
/* Free the current output list and make the new list current. */
|
|
XLListFree (all_outputs, FreeSingleOutput);
|
|
all_outputs = new_list;
|
|
|
|
if (any_change)
|
|
{
|
|
/* If something changed, clear each surface's output region. We
|
|
rely on the window manager to send a ConfigureNotify event
|
|
and move the windows around appropriately. */
|
|
|
|
surface = all_surfaces.next;
|
|
for (; surface != &all_surfaces; surface = surface->next)
|
|
{
|
|
pixman_region32_clear (&surface->output_region);
|
|
|
|
if (surface->role
|
|
&& surface->role->funcs.outputs_changed)
|
|
surface->role->funcs.outputs_changed (surface, surface->role);
|
|
}
|
|
}
|
|
}
|
|
|
|
static double
|
|
GetCurrentRefresh (Output *output)
|
|
{
|
|
Mode *mode;
|
|
XLList *list;
|
|
|
|
for (list = output->modes; list; list = list->next)
|
|
{
|
|
mode = list->data;
|
|
|
|
if (mode->flags & WL_OUTPUT_MODE_CURRENT)
|
|
return (double) mode->refresh / 1000;
|
|
}
|
|
|
|
/* No current mode! */
|
|
return 0.0;
|
|
}
|
|
|
|
static Output *
|
|
GetOutputAt (int x, int y)
|
|
{
|
|
Output *output;
|
|
XLList *tem;
|
|
int x1, y1, x2, y2;
|
|
|
|
for (tem = all_outputs; tem; tem = tem->next)
|
|
{
|
|
output = tem->data;
|
|
|
|
x1 = output->x;
|
|
y1 = output->y;
|
|
x2 = output->x + output->width - 1;
|
|
y2 = output->y + output->height - 1;
|
|
|
|
if (x >= x1 && x <= x2
|
|
&& y >= y1 && y <= y2)
|
|
return output;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Bool
|
|
AnyIntersectionBetween (int x, int y, int x1, int y1,
|
|
int x2, int y2, int x3, int y3)
|
|
{
|
|
return (x < x3 && x1 > x2 && y < y3 && y1 > y2);
|
|
}
|
|
|
|
static int
|
|
ComputeSurfaceOutputs (int x, int y, int width, int height,
|
|
int noutputs, Output **outputs)
|
|
{
|
|
XLList *tem;
|
|
Output *output;
|
|
int i;
|
|
|
|
i = 0;
|
|
|
|
for (tem = all_outputs; tem; tem = tem->next)
|
|
{
|
|
output = tem->data;
|
|
|
|
/* Test all four corners in case some extend outside the screen
|
|
or output. */
|
|
if (AnyIntersectionBetween (x, y, x + width - 1, y + height - 1,
|
|
output->x, output->y,
|
|
output->x + output->width - 1,
|
|
output->y + output->height - 1)
|
|
&& i + 1 < noutputs)
|
|
outputs[i++] = output;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static Bool
|
|
FindOutput (Output **outputs, int noutputs, RROutput id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < noutputs; ++i)
|
|
{
|
|
if (outputs[i]->output == id)
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
static struct wl_resource *
|
|
FindOutputResource (Output *output, Surface *client_surface)
|
|
{
|
|
struct wl_client *client;
|
|
XLList *tem;
|
|
|
|
client = wl_resource_get_client (client_surface->resource);
|
|
|
|
for (tem = output->resources; tem; tem = tem->next)
|
|
{
|
|
if (wl_resource_get_client (tem->data) == client)
|
|
return tem->data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Bool
|
|
BoxContains (pixman_box32_t *box, int x, int y, int width,
|
|
int height)
|
|
{
|
|
return (x >= box->x1 && y >= box->y1
|
|
&& x + width <= box->x2
|
|
&& y + height <= box->y2);
|
|
}
|
|
|
|
void
|
|
XLUpdateSurfaceOutputs (Surface *surface, int x, int y, int width,
|
|
int height)
|
|
{
|
|
Output *outputs[256], *output;
|
|
int n, i;
|
|
struct wl_resource *resource;
|
|
|
|
if (width == -1)
|
|
width = ViewWidth (surface->view);
|
|
|
|
if (height == -1)
|
|
height = ViewHeight (surface->view);
|
|
|
|
if (BoxContains (pixman_region32_extents (&surface->output_region),
|
|
x, y, width, height))
|
|
/* The surface didn't move past the output region. */
|
|
return;
|
|
|
|
/* TODO: store the number of outputs somewhere instead of hardcoding
|
|
256. */
|
|
n = ComputeSurfaceOutputs (x, y, width, height,
|
|
ArrayElements (outputs),
|
|
outputs);
|
|
|
|
/* First, find and leave all the outputs that the surface is no
|
|
longer inside. */
|
|
for (i = 0; i < surface->n_outputs; ++i)
|
|
{
|
|
if (!FindOutput (outputs, n, surface->outputs[i]))
|
|
{
|
|
output = FindOutputById (surface->outputs[i]);
|
|
|
|
if (!output)
|
|
continue;
|
|
|
|
resource = FindOutputResource (output, surface);
|
|
|
|
if (resource)
|
|
wl_surface_send_leave (surface->resource, resource);
|
|
}
|
|
}
|
|
|
|
/* Then, send enter events for all the outputs that the surface has
|
|
not previously entered. Also calculate a rectangle defining an
|
|
area in which output recomputation need not take place. */
|
|
|
|
if (!n)
|
|
pixman_region32_init (&surface->output_region);
|
|
else
|
|
pixman_region32_clear (&surface->output_region);
|
|
|
|
for (i = 0; i < n; ++i)
|
|
{
|
|
if (!FindOutputId (surface->outputs, surface->n_outputs,
|
|
outputs[i]->output))
|
|
{
|
|
resource = FindOutputResource (outputs[i], surface);
|
|
|
|
if (resource)
|
|
wl_surface_send_enter (surface->resource, resource);
|
|
}
|
|
|
|
if (!i)
|
|
pixman_region32_init_rect (&surface->output_region,
|
|
outputs[i]->x, outputs[i]->y,
|
|
outputs[i]->width,
|
|
outputs[i]->height);
|
|
else
|
|
pixman_region32_intersect_rect (&surface->output_region,
|
|
&surface->output_region,
|
|
outputs[i]->x,
|
|
outputs[i]->y,
|
|
outputs[i]->width,
|
|
outputs[i]->height);
|
|
}
|
|
|
|
/* Copy the list of outputs to the surface as well. */
|
|
surface->n_outputs = n;
|
|
|
|
if (surface->n_outputs)
|
|
surface->outputs = XLRealloc (surface->outputs,
|
|
sizeof *surface->outputs
|
|
* surface->n_outputs);
|
|
else
|
|
{
|
|
XLFree (surface->outputs);
|
|
surface->outputs = NULL;
|
|
}
|
|
|
|
for (i = 0; i < n; ++i)
|
|
surface->outputs[i] = outputs[i]->output;
|
|
|
|
/* At the same time, also update outputs for attached
|
|
subsurfaces. */
|
|
XLUpdateOutputsForChildren (surface, x, y);
|
|
|
|
/* And make a record of the coordinates that were used to compute
|
|
outputs for this surface. */
|
|
surface->output_x = x;
|
|
surface->output_y = y;
|
|
}
|
|
|
|
Bool
|
|
XLGetOutputRectAt (int x, int y, int *x_out, int *y_out,
|
|
int *width_out, int *height_out)
|
|
{
|
|
Output *output;
|
|
|
|
output = GetOutputAt (x, y);
|
|
|
|
/* There is no output at this position. */
|
|
if (!output)
|
|
return False;
|
|
|
|
if (x_out)
|
|
*x_out = output->x;
|
|
|
|
if (y_out)
|
|
*y_out = output->y;
|
|
|
|
if (width_out)
|
|
*width_out = output->width;
|
|
|
|
if (height_out)
|
|
*height_out = output->height;
|
|
|
|
return True;
|
|
}
|
|
|
|
Bool
|
|
XLHandleOneXEventForOutputs (XEvent *event)
|
|
{
|
|
XRRNotifyEvent *notify;
|
|
XRROutputPropertyNotifyEvent *property;
|
|
XRRResourceChangeNotifyEvent *resource;
|
|
Time time;
|
|
|
|
notify = (XRRNotifyEvent *) event;
|
|
|
|
if (event->type == compositor.rr_event_base + RRNotify)
|
|
{
|
|
NoticeOutputsMaybeChanged ();
|
|
|
|
if (change_hook)
|
|
{
|
|
time = CurrentTime;
|
|
|
|
/* See if a timestamp of some sort can be extracted. */
|
|
switch (notify->subtype)
|
|
{
|
|
case RRNotify_OutputProperty:
|
|
property = (XRROutputPropertyNotifyEvent *) notify;
|
|
time = property->timestamp;
|
|
break;
|
|
|
|
case RRNotify_ResourceChange:
|
|
resource = (XRRResourceChangeNotifyEvent *) notify;
|
|
time = resource->timestamp;
|
|
break;
|
|
}
|
|
|
|
change_hook (time);
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
if (event->type == compositor.rr_event_base + RRScreenChangeNotify)
|
|
{
|
|
XRRUpdateConfiguration (event);
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
void
|
|
XLOutputGetMinRefresh (struct timespec *timespec)
|
|
{
|
|
Output *output;
|
|
XLList *list;
|
|
double min_refresh, t, between;
|
|
|
|
timespec->tv_sec = 0;
|
|
timespec->tv_nsec = 16000000 * 2;
|
|
min_refresh = 0.0;
|
|
|
|
/* Find the output with the lowest current refresh rate, and use
|
|
that. */
|
|
for (list = all_outputs; list; list = list->next)
|
|
{
|
|
output = list->data;
|
|
t = GetCurrentRefresh (output);
|
|
|
|
if (t < min_refresh || min_refresh == 0.0)
|
|
min_refresh = t;
|
|
}
|
|
|
|
/* min_refresh can be 0.0 if there are no valid modes. In that
|
|
case, fall back to 30 fps. */
|
|
if (min_refresh == 0.0)
|
|
{
|
|
fprintf (stderr, "Warning: defaulting to 30fps refresh rate\n");
|
|
return;
|
|
}
|
|
|
|
/* This is vblank + the time it takes to scan a frame, in
|
|
seconds. */
|
|
between = 1.0 / min_refresh;
|
|
|
|
/* Now populate the timespec with it. */
|
|
timespec->tv_nsec = MIN (1000000000, between * 1000000000);
|
|
timespec->tv_sec = (between - timespec->tv_nsec) / 1000000000;
|
|
}
|
|
|
|
static void
|
|
RunScaleChangeCallbacks (void)
|
|
{
|
|
ScaleChangeCallback *callback;
|
|
|
|
callback = scale_callbacks.next;
|
|
|
|
while (callback != &scale_callbacks)
|
|
{
|
|
callback->scale_change (callback->data,
|
|
global_scale_factor);
|
|
callback = callback->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
HandleScaleChange (int scale)
|
|
{
|
|
Output *output;
|
|
XLList *list;
|
|
|
|
if (scale == global_scale_factor)
|
|
return;
|
|
|
|
/* Update the scale factor. */
|
|
global_scale_factor = scale;
|
|
|
|
/* Update the scale for each output and send changes. */
|
|
for (list = all_outputs; list; list = list->next)
|
|
{
|
|
output = list->data;
|
|
output->scale = scale;
|
|
|
|
SendUpdates (output, ScaleChanged);
|
|
}
|
|
|
|
/* Now, run every scale change function. */
|
|
RunScaleChangeCallbacks ();
|
|
}
|
|
|
|
static void
|
|
HandleScaleSettingChange (int scale)
|
|
{
|
|
real_scale_factor = scale;
|
|
|
|
/* If the scale factor is locked, just return. */
|
|
if (locked_output_scale)
|
|
return;
|
|
|
|
/* Otherwise, update the scale. */
|
|
HandleScaleChange (scale);
|
|
}
|
|
|
|
void *
|
|
XLAddScaleChangeCallback (void *data, void (*func) (void *, int))
|
|
{
|
|
ScaleChangeCallback *callback;
|
|
|
|
callback = XLMalloc (sizeof *callback);
|
|
callback->next = scale_callbacks.next;
|
|
callback->last = &scale_callbacks;
|
|
scale_callbacks.next->last = callback;
|
|
scale_callbacks.next = callback;
|
|
|
|
callback->scale_change = func;
|
|
callback->data = data;
|
|
|
|
return callback;
|
|
}
|
|
|
|
void
|
|
XLRemoveScaleChangeCallback (void *key)
|
|
{
|
|
ScaleChangeCallback *callback;
|
|
|
|
callback = key;
|
|
callback->last->next = callback->next;
|
|
callback->next->last = callback->last;
|
|
|
|
XLFree (callback);
|
|
}
|
|
|
|
void
|
|
XLClearOutputs (Surface *surface)
|
|
{
|
|
Output *output;
|
|
int i;
|
|
struct wl_resource *resource;
|
|
|
|
for (i = 0; i < surface->n_outputs; ++i)
|
|
{
|
|
output = FindOutputById (surface->outputs[i]);
|
|
|
|
if (!output)
|
|
continue;
|
|
|
|
resource = FindOutputResource (output, surface);
|
|
|
|
if (!resource)
|
|
continue;
|
|
|
|
wl_surface_send_leave (surface->resource, resource);
|
|
}
|
|
|
|
XLFree (surface->outputs);
|
|
surface->outputs = NULL;
|
|
surface->n_outputs = 0;
|
|
}
|
|
|
|
void
|
|
XLOutputSetChangeFunction (void (*change_func) (Time))
|
|
{
|
|
change_hook = change_func;
|
|
}
|
|
|
|
void
|
|
XLGetMaxOutputBounds (int *x_min, int *y_min, int *x_max,
|
|
int *y_max)
|
|
{
|
|
int x1, y1, x2, y2;
|
|
Output *output;
|
|
XLList *tem;
|
|
|
|
x1 = y1 = INT_MAX;
|
|
x2 = y2 = INT_MIN;
|
|
|
|
if (!all_outputs)
|
|
{
|
|
*x_min = 0;
|
|
*y_min = 0;
|
|
*x_max = DisplayWidth (compositor.display,
|
|
DefaultScreen (compositor.display)) - 1;
|
|
*y_max = DisplayHeight (compositor.display,
|
|
DefaultScreen (compositor.display)) - 1;
|
|
return;
|
|
}
|
|
|
|
for (tem = all_outputs; tem; tem = tem->next)
|
|
{
|
|
output = tem->data;
|
|
|
|
if (output->x < x1)
|
|
x1 = output->x;
|
|
|
|
if (output->y < y1)
|
|
y1 = output->y;
|
|
|
|
if (output->x + output->width - 1 > x2)
|
|
x2 = output->x + output->width - 1;
|
|
|
|
if (output->y + output->height - 1 > y2)
|
|
y2 = output->y + output->height - 1;
|
|
}
|
|
|
|
*x_min = x1;
|
|
*y_min = y1;
|
|
*x_max = x2;
|
|
*y_max = y2;
|
|
}
|
|
|
|
void
|
|
XLOutputHandleScaleChange (int new_scale)
|
|
{
|
|
/* If the value is not -1, just use the new scale.
|
|
Otherwise, use the current non-locked scale. */
|
|
|
|
if (new_scale != -1)
|
|
HandleScaleChange (new_scale);
|
|
else
|
|
HandleScaleChange (real_scale_factor);
|
|
}
|
|
|
|
void
|
|
XLInitRROutputs (void)
|
|
{
|
|
Bool extension;
|
|
|
|
extension = XRRQueryExtension (compositor.display,
|
|
&compositor.rr_event_base,
|
|
&compositor.rr_error_base);
|
|
|
|
if (!extension)
|
|
{
|
|
fprintf (stderr, "Display '%s' does not support the RandR extension\n",
|
|
DisplayString (compositor.display));
|
|
abort ();
|
|
}
|
|
|
|
XRRQueryVersion (compositor.display,
|
|
&compositor.rr_major,
|
|
&compositor.rr_minor);
|
|
|
|
if (compositor.rr_major < 1
|
|
|| (compositor.rr_major == 1
|
|
&& compositor.rr_minor < 4))
|
|
{
|
|
fprintf (stderr, "Display '%s' does not support a"
|
|
" sufficiently new version of the RandR extension\n",
|
|
DisplayString (compositor.display));
|
|
abort ();
|
|
}
|
|
|
|
/* Set the initial scale. */
|
|
global_scale_factor = 1;
|
|
|
|
/* Start listening to changes to the scale factor, unless a scale
|
|
factor was specified for debugging. */
|
|
if (!ApplyEnvironment ("GLOBAL_SCALE", &global_scale_factor))
|
|
XLListenToIntegerSetting ("Gdk/WindowScalingFactor",
|
|
HandleScaleSettingChange);
|
|
|
|
/* Set the real scale factor. */
|
|
real_scale_factor = global_scale_factor;
|
|
|
|
/* Initialize the scale change callback list sentinel node. */
|
|
scale_callbacks.next = &scale_callbacks;
|
|
scale_callbacks.last = &scale_callbacks;
|
|
|
|
/* Select for various kinds of required input. */
|
|
XRRSelectInput (compositor.display,
|
|
DefaultRootWindow (compositor.display),
|
|
(RRCrtcChangeNotifyMask
|
|
| RROutputChangeNotifyMask
|
|
| RROutputPropertyNotifyMask
|
|
| RRResourceChangeNotifyMask));
|
|
|
|
all_outputs = BuildOutputTree ();
|
|
MakeGlobalsForOutputTree (all_outputs);
|
|
}
|