10309d3b3Smrg/*
20309d3b3Smrg * Copyright © 2011 Red Hat, Inc.
30309d3b3Smrg *
40309d3b3Smrg * Permission is hereby granted, free of charge, to any person obtaining a
50309d3b3Smrg * copy of this software and associated documentation files (the "Software"),
60309d3b3Smrg * to deal in the Software without restriction, including without limitation
70309d3b3Smrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
80309d3b3Smrg * and/or sell copies of the Software, and to permit persons to whom the
90309d3b3Smrg * Software is furnished to do so, subject to the following conditions:
100309d3b3Smrg *
110309d3b3Smrg * The above copyright notice and this permission notice (including the next
120309d3b3Smrg * paragraph) shall be included in all copies or substantial portions of the
130309d3b3Smrg * Software.
140309d3b3Smrg *
150309d3b3Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
160309d3b3Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
170309d3b3Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
180309d3b3Smrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
190309d3b3Smrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
200309d3b3Smrg * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
210309d3b3Smrg * DEALINGS IN THE SOFTWARE.
220309d3b3Smrg *
230309d3b3Smrg */
240309d3b3Smrg
250309d3b3Smrg
260309d3b3Smrg#include "xinput.h"
270309d3b3Smrg#include <string.h>
280309d3b3Smrg#include <X11/extensions/Xrandr.h>
290309d3b3Smrg#include <X11/extensions/Xinerama.h>
300309d3b3Smrg
310309d3b3Smrg
320309d3b3Smrgtypedef struct Matrix {
330309d3b3Smrg    float m[9];
340309d3b3Smrg} Matrix;
350309d3b3Smrg
360309d3b3Smrgstatic void matrix_set(Matrix *m, int row, int col, float val)
370309d3b3Smrg{
380309d3b3Smrg    m->m[row * 3 + col] = val;
390309d3b3Smrg}
400309d3b3Smrg
410309d3b3Smrgstatic void matrix_set_unity(Matrix *m)
420309d3b3Smrg{
430309d3b3Smrg    memset(m, 0, sizeof(m->m));
440309d3b3Smrg    matrix_set(m, 0, 0, 1);
450309d3b3Smrg    matrix_set(m, 1, 1, 1);
460309d3b3Smrg    matrix_set(m, 2, 2, 1);
470309d3b3Smrg}
480309d3b3Smrg
490309d3b3Smrg#if DEBUG
500309d3b3Smrgstatic void matrix_print(const Matrix *m)
510309d3b3Smrg{
520309d3b3Smrg    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[0], m->m[1], m->m[2]);
530309d3b3Smrg    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[3], m->m[4], m->m[5]);
540309d3b3Smrg    printf("[ %3.3f %3.3f %3.3f ]\n", m->m[6], m->m[7], m->m[8]);
550309d3b3Smrg}
560309d3b3Smrg#endif
570309d3b3Smrg
580309d3b3Smrgstatic int
590309d3b3Smrgapply_matrix(Display *dpy, int deviceid, Matrix *m)
600309d3b3Smrg{
610309d3b3Smrg    Atom prop_float, prop_matrix;
620309d3b3Smrg
630309d3b3Smrg    union {
640309d3b3Smrg        unsigned char *c;
650309d3b3Smrg        float *f;
660309d3b3Smrg    } data;
670309d3b3Smrg    int format_return;
680309d3b3Smrg    Atom type_return;
690309d3b3Smrg    unsigned long nitems;
700309d3b3Smrg    unsigned long bytes_after;
710309d3b3Smrg
720309d3b3Smrg    int rc;
730309d3b3Smrg
740309d3b3Smrg    prop_float = XInternAtom(dpy, "FLOAT", False);
750309d3b3Smrg    prop_matrix = XInternAtom(dpy, "Coordinate Transformation Matrix", False);
760309d3b3Smrg
770309d3b3Smrg    if (!prop_float)
780309d3b3Smrg    {
790309d3b3Smrg        fprintf(stderr, "Float atom not found. This server is too old.\n");
800309d3b3Smrg        return EXIT_FAILURE;
810309d3b3Smrg    }
820309d3b3Smrg    if (!prop_matrix)
830309d3b3Smrg    {
840309d3b3Smrg        fprintf(stderr, "Coordinate transformation matrix not found. This "
850309d3b3Smrg                "server is too old\n");
860309d3b3Smrg        return EXIT_FAILURE;
870309d3b3Smrg    }
880309d3b3Smrg
890309d3b3Smrg    rc = XIGetProperty(dpy, deviceid, prop_matrix, 0, 9, False, prop_float,
900309d3b3Smrg                       &type_return, &format_return, &nitems, &bytes_after,
910309d3b3Smrg                       &data.c);
920309d3b3Smrg    if (rc != Success || prop_float != type_return || format_return != 32 ||
930309d3b3Smrg        nitems != 9 || bytes_after != 0)
940309d3b3Smrg    {
950309d3b3Smrg        fprintf(stderr, "Failed to retrieve current property values\n");
960309d3b3Smrg        return EXIT_FAILURE;
970309d3b3Smrg    }
980309d3b3Smrg
990309d3b3Smrg    memcpy(data.f, m->m, sizeof(m->m));
1000309d3b3Smrg
1010309d3b3Smrg    XIChangeProperty(dpy, deviceid, prop_matrix, prop_float,
1020309d3b3Smrg                     format_return, PropModeReplace, data.c, nitems);
1030309d3b3Smrg
1040309d3b3Smrg    XFree(data.c);
1050309d3b3Smrg
1060309d3b3Smrg    return EXIT_SUCCESS;
1070309d3b3Smrg}
1080309d3b3Smrg
10933734831Smrgstatic void
11033734831Smrgmatrix_s4(Matrix *m, float x02, float x12, float d1, float d2, int main_diag)
11133734831Smrg{
11233734831Smrg    matrix_set(m, 0, 2, x02);
11333734831Smrg    matrix_set(m, 1, 2, x12);
11433734831Smrg
11533734831Smrg    if (main_diag) {
11633734831Smrg        matrix_set(m, 0, 0, d1);
11733734831Smrg        matrix_set(m, 1, 1, d2);
11833734831Smrg    } else {
11933734831Smrg        matrix_set(m, 0, 0, 0);
12033734831Smrg        matrix_set(m, 1, 1, 0);
12133734831Smrg        matrix_set(m, 0, 1, d1);
12233734831Smrg        matrix_set(m, 1, 0, d2);
12333734831Smrg    }
12433734831Smrg}
12533734831Smrg
12633734831Smrg#define RR_Reflect_All	(RR_Reflect_X|RR_Reflect_Y)
12733734831Smrg
1280309d3b3Smrgstatic void
1290309d3b3Smrgset_transformation_matrix(Display *dpy, Matrix *m, int offset_x, int offset_y,
13033734831Smrg                          int screen_width, int screen_height,
13133734831Smrg                          int rotation)
1320309d3b3Smrg{
13333734831Smrg    /* total display size */
1340309d3b3Smrg    int width = DisplayWidth(dpy, DefaultScreen(dpy));
1350309d3b3Smrg    int height = DisplayHeight(dpy, DefaultScreen(dpy));
1360309d3b3Smrg
1370309d3b3Smrg    /* offset */
1380309d3b3Smrg    float x = 1.0 * offset_x/width;
1390309d3b3Smrg    float y = 1.0 * offset_y/height;
1400309d3b3Smrg
1410309d3b3Smrg    /* mapping */
1420309d3b3Smrg    float w = 1.0 * screen_width/width;
1430309d3b3Smrg    float h = 1.0 * screen_height/height;
1440309d3b3Smrg
1450309d3b3Smrg    matrix_set_unity(m);
1460309d3b3Smrg
14733734831Smrg    /*
14833734831Smrg     * There are 16 cases:
14933734831Smrg     * Rotation X Reflection
15033734831Smrg     * Rotation: 0 | 90 | 180 | 270
15133734831Smrg     * Reflection: None | X | Y | XY
15233734831Smrg     *
15333734831Smrg     * They are spelled out instead of doing matrix multiplication to avoid
15433734831Smrg     * any floating point errors.
15533734831Smrg     */
15633734831Smrg    switch (rotation) {
15733734831Smrg    case RR_Rotate_0:
15833734831Smrg    case RR_Rotate_180 | RR_Reflect_All:
15933734831Smrg        matrix_s4(m, x, y, w, h, 1);
16033734831Smrg        break;
16133734831Smrg    case RR_Reflect_X|RR_Rotate_0:
16233734831Smrg    case RR_Reflect_Y|RR_Rotate_180:
16333734831Smrg        matrix_s4(m, x + w, y, -w, h, 1);
16433734831Smrg        break;
16533734831Smrg    case RR_Reflect_Y|RR_Rotate_0:
16633734831Smrg    case RR_Reflect_X|RR_Rotate_180:
16733734831Smrg        matrix_s4(m, x, y + h, w, -h, 1);
16833734831Smrg        break;
16933734831Smrg    case RR_Rotate_90:
17033734831Smrg    case RR_Rotate_270 | RR_Reflect_All: /* left limited - correct in working zone. */
17133734831Smrg        matrix_s4(m, x + w, y, -w, h, 0);
17233734831Smrg        break;
17333734831Smrg    case RR_Rotate_270:
17433734831Smrg    case RR_Rotate_90 | RR_Reflect_All: /* left limited - correct in working zone. */
17533734831Smrg        matrix_s4(m, x, y + h, w, -h, 0);
17633734831Smrg        break;
17733734831Smrg    case RR_Rotate_90 | RR_Reflect_X: /* left limited - correct in working zone. */
17833734831Smrg    case RR_Rotate_270 | RR_Reflect_Y: /* left limited - correct in working zone. */
17933734831Smrg        matrix_s4(m, x, y, w, h, 0);
18033734831Smrg        break;
18133734831Smrg    case RR_Rotate_90 | RR_Reflect_Y: /* right limited - correct in working zone. */
18233734831Smrg    case RR_Rotate_270 | RR_Reflect_X: /* right limited - correct in working zone. */
18333734831Smrg        matrix_s4(m, x + w, y + h, -w, -h, 0);
18433734831Smrg        break;
18533734831Smrg    case RR_Rotate_180:
18633734831Smrg    case RR_Reflect_All|RR_Rotate_0:
18733734831Smrg        matrix_s4(m, x + w, y + h, -w, -h, 1);
18833734831Smrg        break;
18933734831Smrg    }
1900309d3b3Smrg
1910309d3b3Smrg#if DEBUG
1920309d3b3Smrg    matrix_print(m);
1930309d3b3Smrg#endif
1940309d3b3Smrg}
1950309d3b3Smrg
1960309d3b3Smrg/* Caller must free return value */
1970309d3b3Smrgstatic XRROutputInfo*
1980309d3b3Smrgfind_output_xrandr(Display *dpy, const char *output_name)
1990309d3b3Smrg{
2000309d3b3Smrg    XRRScreenResources *res;
2010309d3b3Smrg    XRROutputInfo *output_info = NULL;
2020309d3b3Smrg    int i;
2030309d3b3Smrg    int found = 0;
2040309d3b3Smrg
2050309d3b3Smrg    res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
2060309d3b3Smrg
2070309d3b3Smrg    for (i = 0; i < res->noutput && !found; i++)
2080309d3b3Smrg    {
2090309d3b3Smrg        output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
2100309d3b3Smrg
2110309d3b3Smrg        if (output_info->crtc && output_info->connection == RR_Connected &&
2120309d3b3Smrg            strcmp(output_info->name, output_name) == 0)
2130309d3b3Smrg        {
2140309d3b3Smrg            found = 1;
2150309d3b3Smrg            break;
2160309d3b3Smrg        }
2170309d3b3Smrg
2180309d3b3Smrg        XRRFreeOutputInfo(output_info);
2190309d3b3Smrg    }
2200309d3b3Smrg
2210309d3b3Smrg    XRRFreeScreenResources(res);
2220309d3b3Smrg
2230309d3b3Smrg    if (!found)
2240309d3b3Smrg        output_info = NULL;
2250309d3b3Smrg
2260309d3b3Smrg    return output_info;
2270309d3b3Smrg}
2280309d3b3Smrg
2290309d3b3Smrgstatic int
2300309d3b3Smrgmap_output_xrandr(Display *dpy, int deviceid, const char *output_name)
2310309d3b3Smrg{
2320309d3b3Smrg    int rc = EXIT_FAILURE;
2330309d3b3Smrg    XRRScreenResources *res;
2340309d3b3Smrg    XRROutputInfo *output_info;
2350309d3b3Smrg
2360309d3b3Smrg    res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
2370309d3b3Smrg    output_info = find_output_xrandr(dpy, output_name);
2380309d3b3Smrg
2390309d3b3Smrg    /* crtc holds our screen info, need to compare to actual screen size */
2400309d3b3Smrg    if (output_info)
2410309d3b3Smrg    {
2420309d3b3Smrg        XRRCrtcInfo *crtc_info;
2430309d3b3Smrg        Matrix m;
2440309d3b3Smrg        matrix_set_unity(&m);
2450309d3b3Smrg        crtc_info = XRRGetCrtcInfo (dpy, res, output_info->crtc);
2460309d3b3Smrg        set_transformation_matrix(dpy, &m, crtc_info->x, crtc_info->y,
24733734831Smrg                                  crtc_info->width, crtc_info->height,
24833734831Smrg                                  crtc_info->rotation);
2490309d3b3Smrg        rc = apply_matrix(dpy, deviceid, &m);
2500309d3b3Smrg        XRRFreeCrtcInfo(crtc_info);
2510309d3b3Smrg        XRRFreeOutputInfo(output_info);
2520309d3b3Smrg    } else
2530309d3b3Smrg        printf("Unable to find output '%s'. "
2540309d3b3Smrg                "Output may not be connected.\n", output_name);
2550309d3b3Smrg
2560309d3b3Smrg    XRRFreeScreenResources(res);
2570309d3b3Smrg
2580309d3b3Smrg    return rc;
2590309d3b3Smrg}
2600309d3b3Smrg
2610309d3b3Smrgstatic int
2620309d3b3Smrgmap_output_xinerama(Display *dpy, int deviceid, const char *output_name)
2630309d3b3Smrg{
2640309d3b3Smrg    const char *prefix = "HEAD-";
2650309d3b3Smrg    XineramaScreenInfo *screens = NULL;
2660309d3b3Smrg    int rc = EXIT_FAILURE;
2670309d3b3Smrg    int event, error;
2680309d3b3Smrg    int nscreens;
2690309d3b3Smrg    int head;
2700309d3b3Smrg    Matrix m;
2710309d3b3Smrg
2720309d3b3Smrg    if (!XineramaQueryExtension(dpy, &event, &error))
2730309d3b3Smrg    {
2740309d3b3Smrg        fprintf(stderr, "Unable to set screen mapping. Xinerama extension not found\n");
2750309d3b3Smrg        goto out;
2760309d3b3Smrg    }
2770309d3b3Smrg
2780309d3b3Smrg    if (strlen(output_name) < strlen(prefix) + 1 ||
2790309d3b3Smrg        strncmp(output_name, prefix, strlen(prefix)) != 0)
2800309d3b3Smrg    {
2810309d3b3Smrg        fprintf(stderr, "Please specify the output name as HEAD-X,"
2820309d3b3Smrg                "where X is the screen number\n");
2830309d3b3Smrg        goto out;
2840309d3b3Smrg    }
2850309d3b3Smrg
2860309d3b3Smrg    head = output_name[strlen(prefix)] - '0';
2870309d3b3Smrg
2880309d3b3Smrg    screens = XineramaQueryScreens(dpy, &nscreens);
2890309d3b3Smrg
2900309d3b3Smrg    if (nscreens == 0)
2910309d3b3Smrg    {
2920309d3b3Smrg        fprintf(stderr, "Xinerama failed to query screens.\n");
2930309d3b3Smrg        goto out;
2940309d3b3Smrg    } else if (nscreens <= head)
2950309d3b3Smrg    {
2960309d3b3Smrg        fprintf(stderr, "Found %d screens, but you requested %s.\n",
2970309d3b3Smrg                nscreens, output_name);
2980309d3b3Smrg        goto out;
2990309d3b3Smrg    }
3000309d3b3Smrg
3010309d3b3Smrg    matrix_set_unity(&m);
3020309d3b3Smrg    set_transformation_matrix(dpy, &m,
3030309d3b3Smrg                              screens[head].x_org, screens[head].y_org,
30433734831Smrg                              screens[head].width, screens[head].height,
30533734831Smrg                              RR_Rotate_0);
3060309d3b3Smrg    rc = apply_matrix(dpy, deviceid, &m);
3070309d3b3Smrg
3080309d3b3Smrgout:
3090309d3b3Smrg    XFree(screens);
3100309d3b3Smrg    return rc;
3110309d3b3Smrg}
3120309d3b3Smrg
3130309d3b3Smrgint
3140309d3b3Smrgmap_to_output(Display *dpy, int argc, char *argv[], char *name, char *desc)
3150309d3b3Smrg{
3160309d3b3Smrg    char *output_name;
3170309d3b3Smrg    XIDeviceInfo *info;
3180309d3b3Smrg    XRROutputInfo *output_info;
3190309d3b3Smrg
3200309d3b3Smrg    if (argc < 2)
3210309d3b3Smrg    {
3220309d3b3Smrg        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
3230309d3b3Smrg        return EXIT_FAILURE;
3240309d3b3Smrg    }
3250309d3b3Smrg
3260309d3b3Smrg    info = xi2_find_device_info(dpy, argv[0]);
3270309d3b3Smrg    if (!info)
3280309d3b3Smrg    {
3290309d3b3Smrg        fprintf(stderr, "unable to find device '%s'\n", argv[0]);
3300309d3b3Smrg        return EXIT_FAILURE;
3310309d3b3Smrg    }
3320309d3b3Smrg
3330309d3b3Smrg    output_name = argv[1];
33420f5670eSmrg
33520f5670eSmrg    if (!strcmp("all", output_name))
33620f5670eSmrg    {
33720f5670eSmrg        Matrix m;
33820f5670eSmrg        matrix_set_unity(&m);
33920f5670eSmrg        return apply_matrix(dpy, info->deviceid, &m);
34020f5670eSmrg    }
34120f5670eSmrg
3420309d3b3Smrg    output_info = find_output_xrandr(dpy, output_name);
3430309d3b3Smrg    if (!output_info)
3440309d3b3Smrg    {
3450309d3b3Smrg        /* Output doesn't exist. Is this a (partial) non-RandR setup?  */
3460309d3b3Smrg        output_info = find_output_xrandr(dpy, "default");
3470309d3b3Smrg        if (output_info)
3480309d3b3Smrg        {
3490309d3b3Smrg            XRRFreeOutputInfo(output_info);
3500309d3b3Smrg            if (strncmp("HEAD-", output_name, strlen("HEAD-")) == 0)
3510309d3b3Smrg                return map_output_xinerama(dpy, info->deviceid, output_name);
3520309d3b3Smrg        }
3530309d3b3Smrg    } else
3540309d3b3Smrg        XRRFreeOutputInfo(output_info);
3550309d3b3Smrg
3560309d3b3Smrg    return map_output_xrandr(dpy, info->deviceid, output_name);
3570309d3b3Smrg}
358