1/*
2 * Copyright © 2011 Red Hat, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
25
26#include "xinput.h"
27#include <string.h>
28#include <X11/extensions/Xrandr.h>
29#include <X11/extensions/Xinerama.h>
30
31
32typedef struct Matrix {
33    float m[9];
34} Matrix;
35
36static void matrix_set(Matrix *m, int row, int col, float val)
37{
38    m->m[row * 3 + col] = val;
39}
40
41static void matrix_set_unity(Matrix *m)
42{
43    memset(m, 0, sizeof(m->m));
44    matrix_set(m, 0, 0, 1);
45    matrix_set(m, 1, 1, 1);
46    matrix_set(m, 2, 2, 1);
47}
48
49#if DEBUG
50static void matrix_print(const Matrix *m)
51{
52    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[0], m->m[1], m->m[2]);
53    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[3], m->m[4], m->m[5]);
54    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[6], m->m[7], m->m[8]);
55}
56#endif
57
58static int
59apply_matrix(Display *dpy, int deviceid, Matrix *m)
60{
61    Atom prop_float, prop_matrix;
62
63    union {
64        unsigned char *c;
65        float *f;
66    } data;
67    int format_return;
68    Atom type_return;
69    unsigned long nitems;
70    unsigned long bytes_after;
71
72    int rc;
73
74    prop_float = XInternAtom(dpy, "FLOAT", False);
75    prop_matrix = XInternAtom(dpy, "Coordinate Transformation Matrix", False);
76
77    if (!prop_float)
78    {
79        fprintf(stderr, "Float atom not found. This server is too old.\n");
80        return EXIT_FAILURE;
81    }
82    if (!prop_matrix)
83    {
84        fprintf(stderr, "Coordinate transformation matrix not found. This "
85                "server is too old\n");
86        return EXIT_FAILURE;
87    }
88
89    rc = XIGetProperty(dpy, deviceid, prop_matrix, 0, 9, False, prop_float,
90                       &type_return, &format_return, &nitems, &bytes_after,
91                       &data.c);
92    if (rc != Success || prop_float != type_return || format_return != 32 ||
93        nitems != 9 || bytes_after != 0)
94    {
95        fprintf(stderr, "Failed to retrieve current property values\n");
96        return EXIT_FAILURE;
97    }
98
99    memcpy(data.f, m->m, sizeof(m->m));
100
101    XIChangeProperty(dpy, deviceid, prop_matrix, prop_float,
102                     format_return, PropModeReplace, data.c, nitems);
103
104    XFree(data.c);
105
106    return EXIT_SUCCESS;
107}
108
109static void
110matrix_s4(Matrix *m, float x02, float x12, float d1, float d2, int main_diag)
111{
112    matrix_set(m, 0, 2, x02);
113    matrix_set(m, 1, 2, x12);
114
115    if (main_diag) {
116        matrix_set(m, 0, 0, d1);
117        matrix_set(m, 1, 1, d2);
118    } else {
119        matrix_set(m, 0, 0, 0);
120        matrix_set(m, 1, 1, 0);
121        matrix_set(m, 0, 1, d1);
122        matrix_set(m, 1, 0, d2);
123    }
124}
125
126#define RR_Reflect_All	(RR_Reflect_X|RR_Reflect_Y)
127
128static void
129set_transformation_matrix(Display *dpy, Matrix *m, int offset_x, int offset_y,
130                          int screen_width, int screen_height,
131                          int rotation)
132{
133    /* total display size */
134    int width = DisplayWidth(dpy, DefaultScreen(dpy));
135    int height = DisplayHeight(dpy, DefaultScreen(dpy));
136
137    /* offset */
138    float x = 1.0 * offset_x/width;
139    float y = 1.0 * offset_y/height;
140
141    /* mapping */
142    float w = 1.0 * screen_width/width;
143    float h = 1.0 * screen_height/height;
144
145    matrix_set_unity(m);
146
147    /*
148     * There are 16 cases:
149     * Rotation X Reflection
150     * Rotation: 0 | 90 | 180 | 270
151     * Reflection: None | X | Y | XY
152     *
153     * They are spelled out instead of doing matrix multiplication to avoid
154     * any floating point errors.
155     */
156    switch (rotation) {
157    case RR_Rotate_0:
158    case RR_Rotate_180 | RR_Reflect_All:
159        matrix_s4(m, x, y, w, h, 1);
160        break;
161    case RR_Reflect_X|RR_Rotate_0:
162    case RR_Reflect_Y|RR_Rotate_180:
163        matrix_s4(m, x + w, y, -w, h, 1);
164        break;
165    case RR_Reflect_Y|RR_Rotate_0:
166    case RR_Reflect_X|RR_Rotate_180:
167        matrix_s4(m, x, y + h, w, -h, 1);
168        break;
169    case RR_Rotate_90:
170    case RR_Rotate_270 | RR_Reflect_All: /* left limited - correct in working zone. */
171        matrix_s4(m, x + w, y, -w, h, 0);
172        break;
173    case RR_Rotate_270:
174    case RR_Rotate_90 | RR_Reflect_All: /* left limited - correct in working zone. */
175        matrix_s4(m, x, y + h, w, -h, 0);
176        break;
177    case RR_Rotate_90 | RR_Reflect_X: /* left limited - correct in working zone. */
178    case RR_Rotate_270 | RR_Reflect_Y: /* left limited - correct in working zone. */
179        matrix_s4(m, x, y, w, h, 0);
180        break;
181    case RR_Rotate_90 | RR_Reflect_Y: /* right limited - correct in working zone. */
182    case RR_Rotate_270 | RR_Reflect_X: /* right limited - correct in working zone. */
183        matrix_s4(m, x + w, y + h, -w, -h, 0);
184        break;
185    case RR_Rotate_180:
186    case RR_Reflect_All|RR_Rotate_0:
187        matrix_s4(m, x + w, y + h, -w, -h, 1);
188        break;
189    }
190
191#if DEBUG
192    matrix_print(m);
193#endif
194}
195
196/* Caller must free return value */
197static XRROutputInfo*
198find_output_xrandr(Display *dpy, const char *output_name)
199{
200    XRRScreenResources *res;
201    XRROutputInfo *output_info = NULL;
202    int i;
203    int found = 0;
204
205    res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
206
207    for (i = 0; i < res->noutput && !found; i++)
208    {
209        output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
210
211        if (output_info->crtc && output_info->connection == RR_Connected &&
212            strcmp(output_info->name, output_name) == 0)
213        {
214            found = 1;
215            break;
216        }
217
218        XRRFreeOutputInfo(output_info);
219    }
220
221    XRRFreeScreenResources(res);
222
223    if (!found)
224        output_info = NULL;
225
226    return output_info;
227}
228
229static int
230map_output_xrandr(Display *dpy, int deviceid, const char *output_name)
231{
232    int rc = EXIT_FAILURE;
233    XRRScreenResources *res;
234    XRROutputInfo *output_info;
235
236    res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
237    output_info = find_output_xrandr(dpy, output_name);
238
239    /* crtc holds our screen info, need to compare to actual screen size */
240    if (output_info)
241    {
242        XRRCrtcInfo *crtc_info;
243        Matrix m;
244        matrix_set_unity(&m);
245        crtc_info = XRRGetCrtcInfo (dpy, res, output_info->crtc);
246        set_transformation_matrix(dpy, &m, crtc_info->x, crtc_info->y,
247                                  crtc_info->width, crtc_info->height,
248                                  crtc_info->rotation);
249        rc = apply_matrix(dpy, deviceid, &m);
250        XRRFreeCrtcInfo(crtc_info);
251        XRRFreeOutputInfo(output_info);
252    } else
253        printf("Unable to find output '%s'. "
254                "Output may not be connected.\n", output_name);
255
256    XRRFreeScreenResources(res);
257
258    return rc;
259}
260
261static int
262map_output_xinerama(Display *dpy, int deviceid, const char *output_name)
263{
264    const char *prefix = "HEAD-";
265    XineramaScreenInfo *screens = NULL;
266    int rc = EXIT_FAILURE;
267    int event, error;
268    int nscreens;
269    int head;
270    Matrix m;
271
272    if (!XineramaQueryExtension(dpy, &event, &error))
273    {
274        fprintf(stderr, "Unable to set screen mapping. Xinerama extension not found\n");
275        goto out;
276    }
277
278    if (strlen(output_name) < strlen(prefix) + 1 ||
279        strncmp(output_name, prefix, strlen(prefix)) != 0)
280    {
281        fprintf(stderr, "Please specify the output name as HEAD-X,"
282                "where X is the screen number\n");
283        goto out;
284    }
285
286    head = output_name[strlen(prefix)] - '0';
287
288    screens = XineramaQueryScreens(dpy, &nscreens);
289
290    if (nscreens == 0)
291    {
292        fprintf(stderr, "Xinerama failed to query screens.\n");
293        goto out;
294    } else if (nscreens <= head)
295    {
296        fprintf(stderr, "Found %d screens, but you requested %s.\n",
297                nscreens, output_name);
298        goto out;
299    }
300
301    matrix_set_unity(&m);
302    set_transformation_matrix(dpy, &m,
303                              screens[head].x_org, screens[head].y_org,
304                              screens[head].width, screens[head].height,
305                              RR_Rotate_0);
306    rc = apply_matrix(dpy, deviceid, &m);
307
308out:
309    XFree(screens);
310    return rc;
311}
312
313int
314map_to_output(Display *dpy, int argc, char *argv[], char *name, char *desc)
315{
316    char *output_name;
317    XIDeviceInfo *info;
318    XRROutputInfo *output_info;
319
320    if (argc < 2)
321    {
322        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
323        return EXIT_FAILURE;
324    }
325
326    info = xi2_find_device_info(dpy, argv[0]);
327    if (!info)
328    {
329        fprintf(stderr, "unable to find device '%s'\n", argv[0]);
330        return EXIT_FAILURE;
331    }
332
333    output_name = argv[1];
334
335    if (!strcmp("all", output_name))
336    {
337        Matrix m;
338        matrix_set_unity(&m);
339        return apply_matrix(dpy, info->deviceid, &m);
340    }
341
342    output_info = find_output_xrandr(dpy, output_name);
343    if (!output_info)
344    {
345        /* Output doesn't exist. Is this a (partial) non-RandR setup?  */
346        output_info = find_output_xrandr(dpy, "default");
347        if (output_info)
348        {
349            XRRFreeOutputInfo(output_info);
350            if (strncmp("HEAD-", output_name, strlen("HEAD-")) == 0)
351                return map_output_xinerama(dpy, info->deviceid, output_name);
352        }
353    } else
354        XRRFreeOutputInfo(output_info);
355
356    return map_output_xrandr(dpy, info->deviceid, output_name);
357}
358