dunst/src/x11/screen.c
2017-11-25 01:38:28 +01:00

332 lines
9.4 KiB
C

#include "screen.h"
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <X11/extensions/Xinerama.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/randr.h>
#include <assert.h>
#include <glib.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "src/settings.h"
#include "x.h"
screen_info *screens;
int screens_len;
bool dunst_follow_errored = false;
int randr_event_base = 0;
static int randr_major_version = 0;
static int randr_minor_version = 0;
void randr_init();
void randr_update();
void xinerama_update();
void screen_update_fallback();
static void x_follow_setup_error_handler(void);
static int x_follow_tear_down_error_handler(void);
static int FollowXErrorHandler(Display *display, XErrorEvent *e);
static Window get_focused_window(void);
static double get_xft_dpi_value()
{
static double dpi = -1;
//Only run this once, we don't expect dpi changes during runtime
if (dpi <= -1) {
XrmInitialize();
char *xRMS = XResourceManagerString(xctx.dpy);
if (xRMS == NULL) {
dpi = 0;
return 0;
}
XrmDatabase xDB = XrmGetStringDatabase(xRMS);
char *xrmType;
XrmValue xrmValue;
if (XrmGetResource(xDB, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) {
dpi = strtod(xrmValue.addr, NULL);
} else {
dpi = 0;
}
XrmDestroyDatabase(xDB);
}
return dpi;
}
void init_screens() {
if (!settings.force_xinerama) {
randr_init();
randr_update();
} else {
xinerama_update();
}
}
void alloc_screen_ar(int n)
{
assert(n > 0);
if (n <= screens_len) return;
screens = g_realloc(screens, n * sizeof(screen_info));
memset(screens, 0, n * sizeof(screen_info));
screens_len = n;
}
void randr_init()
{
int randr_error_base = 0;
if (!XRRQueryExtension(xctx.dpy, &randr_event_base, &randr_error_base)) {
fprintf(stderr, "Could not initialize the RandR extension, falling back to single monitor mode.\n");
return;
}
XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version);
XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask);
}
void randr_update()
{
if (randr_major_version < 1
|| (randr_major_version == 1 && randr_minor_version < 5)) {
fprintf(stderr, "Server RandR version too low (%i.%i). Falling back to single monitor mode\n",
randr_major_version,
randr_minor_version);
screen_update_fallback();
return;
}
int n = 0;
XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n);
if (n < 1) {
fprintf(stderr, "Get monitors reported %i monitors, falling back to single monitor mode\n", n);
screen_update_fallback();
return;
}
assert(m);
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].dim.x = m[i].x;
screens[i].dim.y = m[i].y;
screens[i].dim.w = m[i].width;
screens[i].dim.h = m[i].height;
screens[i].dim.mmh = m[i].mheight;
}
XRRFreeMonitors(m);
}
static int autodetect_dpi(screen_info *scr)
{
return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh;
}
void screen_check_event(XEvent event)
{
if (event.type == randr_event_base + RRScreenChangeNotify)
randr_update();
}
void xinerama_update()
{
int n;
XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n);
if (!info) {
fprintf(stderr, "(Xinerama) Could not get screen info, falling back to single monitor mode\n");
screen_update_fallback();
return;
}
alloc_screen_ar(n);
for (int i = 0; i < n; i++) {
screens[i].dim.x = info[i].x_org;
screens[i].dim.y = info[i].y_org;
screens[i].dim.h = info[i].height;
screens[i].dim.w = info[i].width;
}
XFree(info);
}
void screen_update_fallback()
{
alloc_screen_ar(1);
int screen;
if (settings.monitor >= 0)
screen = settings.monitor;
else
screen = DefaultScreen(xctx.dpy);
screens[0].dim.w = DisplayWidth(xctx.dpy, screen);
screens[0].dim.h = DisplayHeight(xctx.dpy, screen);
}
/*
* Select the screen on which the Window
* should be displayed.
*/
screen_info *get_active_screen()
{
int ret = 0;
if (settings.monitor > 0 && settings.monitor < screens_len) {
ret = settings.monitor;
goto sc_cleanup;
}
x_follow_setup_error_handler();
if (settings.f_mode == FOLLOW_NONE) {
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
} else {
int x, y;
assert(settings.f_mode == FOLLOW_MOUSE
|| settings.f_mode == FOLLOW_KEYBOARD);
Window root =
RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
if (settings.f_mode == FOLLOW_MOUSE) {
int dummy;
unsigned int dummy_ui;
Window dummy_win;
XQueryPointer(xctx.dpy,
root,
&dummy_win,
&dummy_win,
&x,
&y,
&dummy,
&dummy,
&dummy_ui);
}
if (settings.f_mode == FOLLOW_KEYBOARD) {
Window focused = get_focused_window();
if (focused == 0) {
/* something went wrong. Fallback to default */
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
}
Window child_return;
XTranslateCoordinates(xctx.dpy, focused, root,
0, 0, &x, &y, &child_return);
}
for (int i = 0; i < screens_len; i++) {
if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y,
screens[i].dim.w, screens[i].dim.h)) {
ret = i;
}
}
if (ret > 0)
goto sc_cleanup;
/* something seems to be wrong. Fallback to default */
ret = XDefaultScreen(xctx.dpy);
goto sc_cleanup;
}
sc_cleanup:
x_follow_tear_down_error_handler();
assert(screens);
assert(ret >= 0 && ret < screens_len);
return &screens[ret];
}
double get_dpi_for_screen(screen_info *scr)
{
double dpi = 0;
if ((!settings.force_xinerama && settings.per_monitor_dpi &&
(dpi = autodetect_dpi(scr))))
return dpi;
else if ((dpi = get_xft_dpi_value()))
return dpi;
else
return 96;
}
/*
* Return the window that currently has
* the keyboard focus.
*/
static Window get_focused_window(void)
{
Window focused = 0;
Atom type;
int format;
unsigned long nitems, bytes_after;
unsigned char *prop_return = NULL;
Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
Atom netactivewindow =
XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false);
XGetWindowProperty(xctx.dpy,
root,
netactivewindow,
0L,
sizeof(Window),
false,
XA_WINDOW,
&type,
&format,
&nitems,
&bytes_after,
&prop_return);
if (prop_return) {
focused = *(Window *)prop_return;
XFree(prop_return);
}
return focused;
}
static void x_follow_setup_error_handler(void)
{
dunst_follow_errored = false;
XFlush(xctx.dpy);
XSetErrorHandler(FollowXErrorHandler);
}
static int x_follow_tear_down_error_handler(void)
{
XFlush(xctx.dpy);
XSync(xctx.dpy, false);
XSetErrorHandler(NULL);
return dunst_follow_errored;
}
static int FollowXErrorHandler(Display *display, XErrorEvent *e)
{
dunst_follow_errored = true;
char err_buf[BUFSIZ];
XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
fputs(err_buf, stderr);
fputs("\n", stderr);
return 0;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */