1 /*
2 Interface to XRANDR
3
4 Copyright 2017-2021 Alexander Kulak.
5 This file is part of alttab program.
6
7 alttab is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 alttab is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with alttab. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <X11/Xlib.h>
22 #include <X11/extensions/Xrandr.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include "alttab.h"
28 #include "util.h"
29 extern Globals g;
30 extern Display *dpy;
31 extern int scr;
32 extern Window root;
33
34 // Documentation:
35 // https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
36
37 // version requirements:
38 // RRGetScreenResourcesCurrent (1.3)
39 #define MAJ_REQ 1
40 #define MIN_REQ 3
41
42 // PRIVATE
43
44 // current output geometry list
45 // we alloc/free them only here
46
47 //
48 // update outs, return nout or 0
49 //
randr_update_outputs(Window w,quad ** outs)50 static int randr_update_outputs(Window w, quad ** outs)
51 {
52 XRRScreenResources *scr_res;
53 XRROutputInfo *out_info;
54 XRRCrtcInfo *crtc_info;
55 int nout;
56 int out; // as returned by randr; may include inactive
57
58 scr_res = XRRGetScreenResourcesCurrent(dpy, w);
59 if (scr_res == NULL)
60 return 0;
61 nout = 0;
62 (*outs) = NULL;
63 for (out = 0; out < scr_res->noutput; out++) {
64 out_info = XRRGetOutputInfo(dpy, scr_res, scr_res->outputs[out]);
65 if (out_info == NULL
66 || out_info->connection != RR_Connected || out_info->crtc == 0) {
67 XRRFreeOutputInfo(out_info); // does it neen NULL check?
68 continue;
69 }
70 crtc_info = XRRGetCrtcInfo(dpy, scr_res, out_info->crtc);
71 if (crtc_info == NULL)
72 continue;
73 (*outs) = realloc((*outs), (nout + 1) * sizeof(quad));
74 if ((*outs) == NULL)
75 return 0; // would be nice to free Infos
76 (*outs)[nout].x = crtc_info->x;
77 (*outs)[nout].y = crtc_info->y;
78 (*outs)[nout].w = crtc_info->width;
79 (*outs)[nout].h = crtc_info->height;
80 msg(0,
81 "output %d (%d by randr) crtc: +%d+%d %dx%d noutput %d npossible %d\n",
82 nout, out,
83 crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height,
84 crtc_info->noutput, crtc_info->npossible);
85 XRRFreeCrtcInfo(crtc_info);
86 XRRFreeOutputInfo(out_info);
87 nout++;
88 }
89 XRRFreeScreenResources(scr_res);
90 return nout;
91 }
92
93 //
94 // return currently focused window and its geometry,
95 // or focus point (depending on vp_mode).
96 // res and fw must be allocated by caller.
97 //
x_get_activity_area(quad * res,Window * fw)98 static bool x_get_activity_area(quad * res, Window * fw)
99 {
100 #define VPM g.option_vp_mode
101 int rtr;
102 Window root_ret, child_ret;
103 int root_x, root_y, win_x, win_y;
104 unsigned int mask_ret;
105
106 *fw = 0;
107
108 if (VPM == VP_FOCUS) {
109 if (XGetInputFocus(dpy, fw, &rtr) == 0 || (*fw) == 0) {
110 msg(-1, "can't recognize focused window\n");
111 return false;
112 }
113 if (!get_absolute_coordinates(*fw, res)) {
114 msg(-1, "can't get window 0x%lx absolute coordinates\n", (*fw));
115 return false;
116 }
117 msg(0, "focus in window 0x%lx (%dx%d +%d+%d)\n",
118 (*fw), res->w, res->h, res->x, res->y);
119 return true;
120 }
121
122 if (VPM == VP_POINTER) {
123 if (!XQueryPointer(dpy, root, &root_ret, &child_ret,
124 &root_x, &root_y, &win_x, &win_y, &mask_ret)) {
125 // pointer is not on the same screen as root
126 return false;
127 }
128 res->x = win_x;
129 res->y = win_y;
130 res->w = res->h = 1;
131 *fw = root;
132 msg(0, "pointer: +%d+%d\n", res->x, res->y);
133 return true;
134 }
135 // unknown vp_mode
136 return false;
137 }
138
139 // PUBLIC
140
141 //
142 // check for randr at runtime
143 // assuming (for now) it's available at build time
144 //
randrAvailable()145 bool randrAvailable()
146 {
147 int maj, min;
148 bool ok;
149 Status s = XRRQueryVersion(dpy, &maj, &min);
150 if (s != 0) {
151 ok = ((maj == MAJ_REQ && min >= MIN_REQ) || (maj > MAJ_REQ)
152 );
153 } else {
154 ok = false;
155 }
156 if (g.debug > 0) {
157 if (s == 0)
158 msg(0, "randr not available\n");
159 else
160 msg(0, "randr v. %d.%d available (%ssufficient)\n",
161 maj, min, ok ? "" : "not ");
162 }
163 return ok;
164 }
165
166 //
167 // return best viewport from randr 'point of view'
168 // of false if not found.
169 // res must be allocated by caller.
170 //
randrGetViewport(quad * res,bool * multihead)171 bool randrGetViewport(quad * res, bool * multihead)
172 {
173 Window fw = 0;
174 quad aq; // 'activity area': focused window geometry or pointer point
175 quad *oq = NULL; // outputs geometries
176 int o, no;
177 int x1, x2, y1, y2, area;
178 quad lq; // largest cross-section at 1st stage
179 int largest_cross_area = 0; // its square
180 int best_1_stage_output = -1;
181 int best_2_stage_output = -1;
182 int smallest_2_stage_area;
183
184 //no = randr_update_outputs(fw != 0 ? fw : root, &oq); // no fw here
185 no = randr_update_outputs(root, &oq);
186 if (no < 1) {
187 msg(0, "randr didn't detect any output\n");
188 *multihead = false;
189 free(oq);
190 return false;
191 }
192 if (no == 1) {
193 msg(0, "using single randr output as viewport\n");
194 *res = oq[0];
195 *multihead = false;
196 free(oq);
197 return true;
198 }
199
200 *multihead = true;
201
202 if (!x_get_activity_area(&aq, &fw)) {
203 msg(0, "failed to detect activity area, using first randr output\n");
204 *res = oq[0];
205 free(oq);
206 return true;
207 }
208
209 for (o = 0; o < no; o++) {
210 if (rectangles_cross(aq, oq[o])) {
211 // this output has a common cross-section with activity area,
212 // now find this cross-section.
213 x1 = aq.x > oq[o].x ? aq.x : oq[o].x;
214 x2 = (aq.x + aq.w) <
215 (oq[o].x + oq[o].w) ? (aq.x + aq.w) : (oq[o].x + oq[o].w);
216 y1 = aq.y > oq[o].y ? aq.y : oq[o].y;
217 y2 = (aq.y + aq.h) <
218 (oq[o].y + oq[o].h) ? (aq.y + aq.h) : (oq[o].y + oq[o].h);
219 area = (x2 - x1) * (y2 - y1);
220 msg(0, "output %d cross-section: %d\n", o, area);
221 if (area > largest_cross_area) {
222 best_1_stage_output = o;
223 lq.x = x1;
224 lq.y = y1;
225 lq.w = x2 - x1;
226 lq.h = y2 - y1;
227 largest_cross_area = area;
228 }
229 /*
230 // disabled feature:
231 // total area of CRTCs which have cross-section with focused window
232 if (oq[o].x < res->x) res->x = oq[o].x;
233 int right_diff = (oq[o].x + oq[o].w) - (res->x + res->w);
234 if (right_diff > 0) res->w += right_diff;
235 if (oq[o].y < res->y) res->y = oq[o].y;
236 int bot_diff = (oq[o].y + oq[o].h) - (res->y + res->h);
237 if (bot_diff > 0) res->h += bot_diff;
238 */
239 } else {
240 msg(0, "output %d doesn't cross with activity area\n", o);
241 }
242 } // outputs
243
244 if (best_1_stage_output == -1) {
245 msg(0,
246 "failed to find largest cross-section, using first randr output\n");
247 *res = oq[0];
248 free(oq);
249 return true;
250 }
251 // if best cross-area is shared with some other monitor,
252 // then smallest of these monitors is choosen.
253 smallest_2_stage_area =
254 oq[best_1_stage_output].w * oq[best_1_stage_output].h;
255 for (o = 0; o < no; o++) {
256 if (o == best_1_stage_output)
257 continue;
258 if (rectangles_cross(lq, oq[o])) {
259 area = oq[o].w * oq[o].h;
260 if (area < smallest_2_stage_area) {
261 best_2_stage_output = o;
262 smallest_2_stage_area = area;
263 }
264 }
265 }
266
267 if (best_2_stage_output == -1) {
268 best_2_stage_output = best_1_stage_output;
269 } else {
270 msg(0,
271 "found better (smallest output) candidate: %d\n",
272 best_2_stage_output);
273 }
274
275 // the single result here is best_2_stage_output
276
277 *res = oq[best_2_stage_output];
278 msg(0,
279 "best viewport from randr: %dx%d +%d+%d\n",
280 res->w, res->h, res->x, res->y);
281 free(oq);
282 return true;
283 }
284