Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
screen.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include <assert.h>
10#include <glib.h>
11#include <locale.h>
12#include <stdbool.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <X11/extensions/randr.h>
17#include <X11/extensions/Xinerama.h>
18#include <X11/extensions/Xrandr.h>
19#include <X11/Xatom.h>
20#include <X11/X.h>
21#include <X11/Xlib.h>
22#include <X11/Xresource.h>
23
24#include "../log.h"
25#include "../settings.h"
26#include "../utils.h"
27#include "x.h"
28#include "screen.h"
29
30struct screen_info *screens;
31int screens_len;
32
33bool dunst_follow_errored = false;
34
35static int randr_major_version = 0;
36static int randr_minor_version = 0;
37
38void randr_init(void);
39void randr_update(void);
40void xinerama_update(void);
41void screen_update_fallback(void);
42static void x_follow_setup_error_handler(void);
43static int x_follow_tear_down_error_handler(void);
44static int FollowXErrorHandler(Display *display, XErrorEvent *e);
45static Window get_focused_window(void);
46
47
57double screen_dpi_xft_cache = -DBL_MAX;
58
59void screen_dpi_xft_cache_purge(void)
60{
61 screen_dpi_xft_cache = -DBL_MAX;
62}
63
64static double screen_dpi_get_from_xft(void)
65{
66 if (screen_dpi_xft_cache == -DBL_MAX) {
68
69 char *xrmType;
70 XrmValue xrmValue;
71 XrmDatabase db = XrmGetDatabase(xctx.dpy);
73 if (XrmGetResource(db, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue))
74 screen_dpi_xft_cache = strtod(xrmValue.addr, NULL);
75 }
77}
78
79static double screen_dpi_get_from_monitor(const struct screen_info *scr)
80{
81 return (double)scr->h * 25.4 / (double)scr->mmh;
82}
83
84double screen_dpi_get(const struct screen_info *scr)
85{
86 if ( ! settings.force_xinerama
87 && settings.per_monitor_dpi)
88 return screen_dpi_get_from_monitor(scr);
89
90 if (screen_dpi_get_from_xft() > 0)
91 return screen_dpi_get_from_xft();
92
93 // Calculate the DPI on the overall screen size.
94 // xrandr --dpi <DPI> does only change the overall screen's millimeters,
95 // but not the physical screen's sizes.
96 //
97 // The screen parameter is XDefaultScreen(), as our scr->id references
98 // the xrandr monitor and not the xrandr screen
99 return ((((double)DisplayWidth (xctx.dpy, XDefaultScreen(xctx.dpy))) * 25.4) /
100 ((double)DisplayWidthMM(xctx.dpy, XDefaultScreen(xctx.dpy))));
101}
102
103void init_screens(void)
104{
105 if (settings.force_xinerama) {
106 xinerama_update();
107 } else {
108 randr_init();
109 randr_update();
110 }
111}
112
113void free_screen_ar(struct screen_info *scr, int len)
114{
115 if (!scr)
116 return;
117
118 for (int i = 0; i < len; ++i) {
119 XFree(scr[i].name);
120 }
121 g_free(scr);
122}
123
124void alloc_screen_ar(int n)
125{
126 assert(n > 0);
127 free_screen_ar(screens, screens_len);
128 screens = g_malloc0(n * sizeof(struct screen_info));
129 screens_len = n;
130}
131
132void randr_init(void)
133{
134 int ignored;
135 if (!XRRQueryExtension(xctx.dpy, &ignored, &ignored)) {
136 LOG_W("Could not initialize the RandR extension. "
137 "Falling back to single monitor mode.");
138 return;
139 }
140 XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version);
141 XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask);
142}
143
144void randr_update(void)
145{
146 if (randr_major_version < 1
147 || (randr_major_version == 1 && randr_minor_version < 5)) {
148 LOG_W("Server RandR version too low (%i.%i). "
149 "Falling back to single monitor mode.",
150 randr_major_version,
151 randr_minor_version);
152 screen_update_fallback();
153 return;
154 }
155
156 int n = 0;
157 XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n);
158
159 if (n < 1) {
160 LOG_C("Get monitors reported %i monitors. "
161 "Falling back to single monitor mode.", n);
162 screen_update_fallback();
163 return;
164 }
165
166 assert(m);
167
168 alloc_screen_ar(n);
169
170 for (int i = 0; i < n; i++) {
171 screens[i].name = XGetAtomName(xctx.dpy, m[i].name);
172 screens[i].id = i;
173 screens[i].x = m[i].x;
174 screens[i].y = m[i].y;
175 screens[i].w = m[i].width;
176 screens[i].h = m[i].height;
177 screens[i].mmh = m[i].mheight;
178 screens[i].dpi = screen_dpi_get(&screens[i]);
179 LOG_I("Detected screen %d (%s)", screens[i].id, screens[i].name);
180 }
181
182 XRRFreeMonitors(m);
183}
184
185bool screen_check_event(XEvent *ev)
186{
187 if (XRRUpdateConfiguration(ev)) {
188 LOG_D("XEvent: processing 'RRScreenChangeNotify'");
189 randr_update();
190
191 return true;
192 }
193 return false;
194}
195
196void xinerama_update(void)
197{
198 int n;
199 XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n);
200
201 if (!info) {
202 LOG_W("Could not get xinerama screen info. "
203 "Falling back to single monitor mode.");
204 screen_update_fallback();
205 return;
206 }
207
208 alloc_screen_ar(n);
209
210 for (int i = 0; i < n; i++) {
211 screens[i].id = i;
212 screens[i].x = info[i].x_org;
213 screens[i].y = info[i].y_org;
214 screens[i].h = info[i].height;
215 screens[i].w = info[i].width;
216 LOG_I("Detected screen %d", screens[i].id);
217 }
218 XFree(info);
219}
220
221void screen_update_fallback(void)
222{
223 alloc_screen_ar(1);
224
225 int screen;
226 if (settings.monitor_num >= 0)
227 screen = settings.monitor_num;
228 else {
229 LOG_D("Ignoring settings.monitor '%s' in fallback mode", settings.monitor);
230 screen = DefaultScreen(xctx.dpy);
231 }
232
233 screens[0].w = DisplayWidth(xctx.dpy, screen);
234 screens[0].h = DisplayHeight(xctx.dpy, screen);
235}
236
237/* see screen.h */
239{
240 return window_is_fullscreen(get_focused_window());
241}
242
246static int XErrorHandlerFullscreen(Display *display, XErrorEvent *e)
247{
248 /* Ignore BadWindow errors. Window may have been gone */
249 if (e->error_code == BadWindow) {
250 return 0;
251 }
252
253 char err_buf[BUFSIZ];
254 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
255 fputs(err_buf, stderr);
256 fputs("\n", stderr);
257
258 return 0;
259}
260
261/* see screen.h */
262bool window_is_fullscreen(Window window)
263{
264 bool fs = false;
265
266 ASSERT_OR_RET(window, false);
267
268 Atom has_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", True);
269 if (has_wm_state == None) {
270 return false;
271 }
272
273 XFlush(xctx.dpy);
274 XSetErrorHandler(XErrorHandlerFullscreen);
275
276 Atom actual_type_return;
277 int actual_format_return;
278 unsigned long bytes_after_return;
279 unsigned char *prop_to_return;
280 unsigned long n_items;
281 int result = XGetWindowProperty(
282 xctx.dpy,
283 window,
284 has_wm_state,
285 0, /* long_offset */
286 sizeof(window), /* long_length */
287 false, /* delete */
288 AnyPropertyType, /* req_type */
289 &actual_type_return,
290 &actual_format_return,
291 &n_items,
292 &bytes_after_return,
293 &prop_to_return);
294
295 XFlush(xctx.dpy);
296 XSync(xctx.dpy, false);
297 XSetErrorHandler(NULL);
298
299 if (result == Success) {
300 for(size_t i = 0; i < n_items; i++) {
301 Atom atom = ((Atom*) prop_to_return)[i];
302 if (!atom)
303 continue;
304
305 char *s = XGetAtomName(xctx.dpy, atom);
306 if (!s)
307 continue;
308
309 if (STR_EQ(s, "_NET_WM_STATE_FULLSCREEN"))
310 fs = true;
311 XFree(s);
312 if (fs)
313 break;
314 }
315 }
316
317 if (prop_to_return)
318 XFree(prop_to_return);
319
320 return fs;
321}
322
323static int get_screen_by_name(const char *name, int defval)
324{
325 for (int i = 0; i < screens_len; ++i) {
326 if (g_strcmp0(screens[i].name, name) == 0) {
327 return i;
328 }
329 }
330 LOG_D("Screen '%s' not found", name);
331 return defval;
332}
333
334/*
335 * Select the screen on which the Window
336 * should be displayed.
337 */
338const struct screen_info *get_active_screen(void)
339{
340 int ret = 0;
341 bool force_follow_mouse = false;
342
343 if (settings.f_mode == FOLLOW_NONE) {
344 if (settings.monitor_num >= 0 && settings.monitor_num < screens_len) {
345 ret = settings.monitor_num;
346 } else {
347 ret = get_screen_by_name(settings.monitor, 0);
348 }
349 goto sc_cleanup;
350 } else {
351 int x, y;
352 assert(settings.f_mode == FOLLOW_MOUSE
353 || settings.f_mode == FOLLOW_KEYBOARD);
354
355 x_follow_setup_error_handler();
356
357 Window root =
358 RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
359
360 if (settings.f_mode == FOLLOW_KEYBOARD) {
361 Window focused = get_focused_window();
362
363 if (!focused) {
364 /*
365 * If no window is focused, or the focus is set
366 * to dynamically change to the root window of
367 * the screen the pointer is on, force following
368 * the mouse.
369 */
370 force_follow_mouse = true;
371 } else {
372 Window child_return;
373 /*
374 * The window with input focus might be on a
375 * different X screen. Use the mouse location
376 * in that case.
377 */
378 force_follow_mouse = !XTranslateCoordinates(
379 xctx.dpy, focused,root,
380 0, 0, &x, &y,
381 &child_return);
382 }
383 }
384
385 if (settings.f_mode == FOLLOW_MOUSE || force_follow_mouse) {
386 int dummy;
387 unsigned int dummy_ui;
388 Window dummy_win;
389
390 XQueryPointer(xctx.dpy,
391 root,
392 &dummy_win,
393 &dummy_win,
394 &x,
395 &y,
396 &dummy,
397 &dummy,
398 &dummy_ui);
399 }
400
401 for (int i = 0; i < screens_len; i++) {
402 if (INRECT(x, y, screens[i].x, screens[i].y,
403 screens[i].w, screens[i].h)) {
404 ret = i;
405 }
406 }
407
408 if (ret > 0)
409 goto sc_cleanup;
410
411 /* something seems to be wrong. Fall back to default */
412 ret = 0;
413 goto sc_cleanup;
414 }
415sc_cleanup:
416 x_follow_tear_down_error_handler();
417 assert(screens);
418 assert(ret >= 0 && ret < screens_len);
419 return &screens[ret];
420}
421
422/*
423 * Return the window that currently has
424 * the keyboard focus.
425 */
426static Window get_focused_window(void)
427{
428 Window focused, root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
429 int ignored;
430
431 XGetInputFocus(xctx.dpy, &focused, &ignored);
432
433 if (focused == None || focused == PointerRoot || focused == root)
434 focused = 0;
435 return focused;
436}
437
438static void x_follow_setup_error_handler(void)
439{
440 dunst_follow_errored = false;
441
442 XFlush(xctx.dpy);
443 XSetErrorHandler(FollowXErrorHandler);
444}
445
446static int x_follow_tear_down_error_handler(void)
447{
448 XFlush(xctx.dpy);
449 XSync(xctx.dpy, false);
450 XSetErrorHandler(NULL);
451 return dunst_follow_errored;
452}
453
454static int FollowXErrorHandler(Display *display, XErrorEvent *e)
455{
456 dunst_follow_errored = true;
457 char err_buf[BUFSIZ];
458 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
459 LOG_W("%s", err_buf);
460
461 return 0;
462}
Logging subsystem and helpers.
double screen_dpi_xft_cache
A cache variable to cache the Xft.dpi xrdb values.
Definition screen.c:57
bool have_fullscreen_window(void)
Find the currently focused window and check if it's in fullscreen mode.
Definition screen.c:238
bool window_is_fullscreen(Window window)
Check if window is in fullscreen mode.
Definition screen.c:262
static int XErrorHandlerFullscreen(Display *display, XErrorEvent *e)
X11 ErrorHandler to mainly discard BadWindow parameter error.
Definition screen.c:246
Xorg screen managment.
Type definitions for settings.
String, time and other various helpers.
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42
Xorg output wrapper.