1/**************************************************************************
2 * Copyright © 2016 VMware, Inc., Palo Alto, CA., USA
3 * All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sub license, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the
14 * next paragraph) shall be included in all copies or substantial portions
15 * of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
20 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 * USE OR OTHER DEALINGS IN THE SOFTWARE.
24 *
25 **************************************************************************/
26
27#ifdef HAVE_CONFIG_H
28#include "config.h"
29#endif
30
31#ifdef HAVE_LIBUDEV
32#include "vmwgfx_driver.h"
33#include <xf86Crtc.h>
34#include "vmwgfx_rr_inlines.h"
35#include "../src/common_compat.h"
36
37#ifndef X_DEBUG
38#define X_DEBUG X_NOTICE
39#endif
40
41/**
42 * struct vmwgfx_layout_box - Struct representing a GUI layout rect
43 *
44 * @x: X value of the origin.
45 * @y: Y value of the origin.
46 * @width: Width of the rect.
47 * @height: Height of the rect.
48 */
49struct vmwgfx_layout_box {
50    int x, y, width, height;
51};
52
53/**
54 * struct vmwgfx_layout - Struct representing a complete GUI layout
55 *
56 * @connected: Number of connected outputs.
57 * @root_width: Width of full desktop.
58 * @root_height: Height of full desktop.
59 * @boxes: Array of GUI layout rects.
60 */
61struct vmwgfx_layout {
62    int connected;
63    int root_width;
64    int root_height;
65    struct vmwgfx_layout_box boxes[];
66};
67
68/**
69 * vmwgfx_layout_debug - Log debug info of a layout struct.
70 *
71 * @pScrn: ScrnInfoPtr: Pointer to the ScrnInfo struct for the screen the
72 * layout should be logged for.
73 * @l1: Pointer to a valid struct vmwgfx_layout.
74 */
75static void
76vmwgfx_layout_debug(ScrnInfoPtr pScrn, const struct vmwgfx_layout *l1)
77{
78    int i;
79
80    xf86DrvMsg(pScrn->scrnIndex, X_DEBUG, "New layout.\n");
81    for (i = 0; i < l1->connected; ++i)
82	xf86DrvMsg(pScrn->scrnIndex, X_DEBUG,
83		   "%d: %d %d %d %d\n", i, l1->boxes[i].x,
84		   l1->boxes[i].y, l1->boxes[i].width, l1->boxes[i].height);
85    xf86DrvMsg(pScrn->scrnIndex, X_DEBUG, "\n");
86}
87
88/**
89 * vmwgfx_layouts_equal - Determine whether two layouts are equal.
90 *
91 * @l1: Pointer to the first struct vmwgfx_layout.
92 * @l2: Pointer to the second struct vmwgfx_layout.
93 *
94 * Returns: TRUE if the layouts are equal. FALSE otherwise.
95 */
96static Bool
97vmwgfx_layouts_equal(const struct vmwgfx_layout *l1,
98		     const struct vmwgfx_layout *l2)
99{
100    if (l1->connected != l2->connected)
101	return FALSE;
102
103    if (!l1->connected)
104	return TRUE;
105
106    return !memcmp(l1->boxes, l2->boxes,
107		   l1->connected*sizeof(struct vmwgfx_layout_box));
108}
109
110/**
111 * vmwgfx_layout_from_kms - Construct a struct vmwgfx_layout from KMS info.
112 *
113 * @pScrn: Pointer to a ScrnInfo struct.
114 *
115 * Returns: A pointer to a newly allocated struct vmwgfx_layout if
116 * successful. NULL otherwise.
117 */
118struct vmwgfx_layout *
119vmwgfx_layout_from_kms(ScrnInfoPtr pScrn)
120{
121    xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(pScrn);
122    int i, connected;
123    struct vmwgfx_layout *layout;
124    size_t size;
125    int min_x = INT_MAX, max_x = INT_MIN, min_y = INT_MAX, max_y = INT_MIN;
126
127    for (i = 0; i < config->num_output; ++i) {
128	xf86OutputPtr output = config->output[i];
129
130	if (!vmwgfx_output_has_origin(output))
131	    return NULL;
132
133	if (output->status != XF86OutputStatusConnected)
134	    break;
135    }
136    connected = i;
137
138    size = offsetof(struct vmwgfx_layout, boxes) +
139	connected * sizeof(struct vmwgfx_layout_box);
140    layout = calloc(1, size);
141    if (!layout)
142	return NULL;
143
144    layout->connected = connected;
145    for (i = 0; i < connected; ++i) {
146	struct vmwgfx_layout_box *box = &layout->boxes[i];
147	xf86OutputPtr output = config->output[i];
148	DisplayModePtr mode = output->probed_modes;
149
150	if (mode == NULL) {
151	    free(layout);
152	    return NULL;
153	}
154
155	vmwgfx_output_origin(output, &box->x, &box->y);
156	box->width = output->probed_modes->HDisplay;
157	box->height = output->probed_modes->VDisplay;
158	min_x = min(min_x, box->x);
159	min_y = min(min_y, box->y);
160	max_x = max(max_x, box->x + box->width);
161	max_y = max(max_y, box->y + box->height);
162    }
163
164    layout->root_width = max_x;
165    layout->root_height = max_y;
166
167    return layout;
168}
169
170/**
171 * vmwgfx_layout_configuration - Set up the screen modesetting configuration
172 * from a struct vmwgfx_layout.
173 *
174 * @pScrn: Pointer to a ScrnInfo struct.
175 * @layout: Layout to use for the new configuration.
176 *
177 * Sets up a new modesetting configuration. Note that the configuration needs
178 * to be committed using xf86SetDesiredModes().
179 */
180void
181vmwgfx_layout_configuration(ScrnInfoPtr pScrn, struct vmwgfx_layout *layout)
182{
183    xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(pScrn);
184    struct vmwgfx_layout_box *box;
185    xf86OutputPtr output;
186    xf86CrtcPtr crtc;
187    int i, j;
188
189    for (j = 0; j < config->num_crtc; ++j) {
190	crtc = config->crtc[j];
191	crtc->enabled = FALSE;
192    }
193
194    for (i = 0, box = layout->boxes; i < config->num_output; ++i, ++box) {
195	output = config->output[i];
196	output->crtc = NULL;
197	if (i >= layout->connected)
198	    continue;
199
200	for (j = 0; j < config->num_crtc; ++j) {
201	    crtc = config->crtc[j];
202	    if (!crtc->enabled && (output->possible_crtcs & (1 << j))) {
203		crtc->enabled = TRUE;
204		output->crtc = crtc;
205		break;
206	    }
207	}
208
209	if (!output->crtc)
210	    continue;
211
212	crtc = output->crtc;
213	xf86SaveModeContents(&crtc->desiredMode, output->probed_modes);
214	crtc->desiredRotation = RR_Rotate_0;
215	crtc->desiredX = box->x;
216	crtc->desiredY = box->y;
217	crtc->desiredTransformPresent = FALSE;
218    }
219}
220
221/**
222 * vmwgfx_layout_handler - Obtain and set a new layout.
223 *
224 * @pScrn: Pointer to a ScrnInfo struct.
225 *
226 * Obtains a new layout from DRM. If the layout differs from the current one,
227 * Try to set the new layout. If that fails, (typically due to root pixmap
228 * resizing issues) try hard to revert to the old layout. Finally
229 * update RandR in a way that tries to block racing display managers
230 * from setting up the layout in a different way.
231 */
232void
233vmwgfx_layout_handler(ScrnInfoPtr pScrn)
234{
235
236    ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
237    modesettingPtr ms = modesettingPTR(pScrn);
238    struct vmwgfx_layout *layout;
239
240    if (!pScreen)
241	return;
242
243    /*
244     * Construct a layout from the new information and determine whether we
245     * need to take action
246     */
247    layout = vmwgfx_layout_from_kms(pScrn);
248    if (layout && (!ms->layout || !vmwgfx_layouts_equal(ms->layout, layout))) {
249	vmwgfx_layout_debug(pScrn, layout);
250	vmwgfx_outputs_off(pScrn);
251	xf86DisableUnusedFunctions(pScrn);
252	if (!vmwgfx_rr_screen_set_size(pScreen, layout->root_width,
253				       layout->root_height)) {
254	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Resizing screen failed.\n");
255	    vmwgfx_outputs_on(pScrn);
256	    free(layout);
257	} else {
258	    vmwgfx_layout_configuration(pScrn, layout);
259	    if (ms->layout)
260	      free(ms->layout);
261	    ms->layout = layout;
262	}
263	xf86SetDesiredModes(pScrn);
264	vmwgfx_notify_rr(pScreen);
265    } else if (layout) {
266	free(layout);
267    }
268}
269
270#endif /* HAVE_LIBUDEV */
271