diff --git a/config.mk b/config.mk index d1ce164..a73ce3f 100644 --- a/config.mk +++ b/config.mk @@ -21,7 +21,7 @@ INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags freetype2` \ `$(PKG_CONFIG) --cflags harfbuzz` -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lgd \ +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lgd -lXrandr \ `$(PKG_CONFIG) --libs fontconfig` \ `$(PKG_CONFIG) --libs freetype2` \ `$(PKG_CONFIG) --libs harfbuzz` diff --git a/keybinds.h b/keybinds.h index 1b315c0..fa5a703 100644 --- a/keybinds.h +++ b/keybinds.h @@ -11,6 +11,7 @@ static Shortcut shortcuts[] = { { ControlMask, XK_equal, zoom, {.f = +1} }, { ControlMask, XK_minus, zoom, {.f = -1} }, { ControlMask, XK_0, zoomreset, {.f = 0} }, + { XK_ANY_MOD, XK_End, refreshxrandr, {.i = 0} }, { ControlMask, XK_y, clipcopy, {.i = 0} }, { ControlMask, XK_p, clippaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, diff --git a/options.h b/options.h index 5893db0..98cb23e 100644 --- a/options.h +++ b/options.h @@ -64,6 +64,19 @@ int allowaltscreen = 1; /* Allow alt screen (1) */ int allowwindowops = 0; /* Allow (insecure) window operations such as setting the clipboard text */ int windowdecorations = 1; /* Display window decoration (1) */ +/* + * Override/adjust fontsize of choosen monitors: + */ +MonitorConfig monitors_config[] = { + /* skip = fixed relative points size (monitor dpi) */ + /* =0 : fixed absolute pixel size (default screen dpi) */ + /* >0 : auto absolute pixel size (monitor dpi) */ + /* <0 : auto relative points size (monitor dpi) */ + {"HDMI-0~2", -14}, + {"LVDS1", -8}, +}; +float winmovethreshold = 0.6; + /* Cursor options */ /* List of cursor styles: diff --git a/x.c b/x.c index ba10a8b..bcb9371 100644 --- a/x.c +++ b/x.c @@ -14,6 +14,7 @@ #include #include #include +#include #include char *argv0; @@ -68,6 +69,19 @@ enum undercurl_slope_type { UNDERCURL_SLOPE_BOTTOM_CAP = 3 }; +typedef struct { + const char *name; + float defaultfontsize; +} MonitorConfig; + +typedef struct { + Atom name; + int x, y, w, h; + float defaultfontsize, usedfontsize; +} MonitorInfo; + +static void refreshxrandr(const Arg *dummy); + /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 @@ -259,6 +273,11 @@ static void (*handler[LASTEvent])(XEvent *) = { [SelectionRequest] = selrequest, }; +static double defaultrelfontsize = 0; +static MonitorInfo *monitors_info = NULL; +static int monitors_num = 0; +static int prev_mindex = -1; + /* Globals */ static DC dc; static XWindow xw; @@ -2429,6 +2448,144 @@ xseturgency(int add) XFree(h); } +static void +cachemonitorinfo() +{ + int prev_num = monitors_num; + MonitorInfo *prev_info = monitors_info; + XRRMonitorInfo *xmonitors = XRRGetMonitors(xw.dpy, XRootWindow(xw.dpy, xw.scr), 1, &monitors_num); + if (!monitors_num) + die("xrandr found no monitors"); + + monitors_info = xmalloc(monitors_num * sizeof(MonitorInfo)); + + for (int i = 0; i < monitors_num; ++i) { + XRRMonitorInfo *xm = &xmonitors[i]; + MonitorInfo *m = &monitors_info[i]; + + m->name = xm->name; + m->x = xm->x; + m->y = xm->y; + m->w = xm->width; + m->h = xm->height; + + float px_mm = ((float)m->w / xm->mwidth + (float)m->h / xm->mheight) / 2; + float px_pt = 25.4 * px_mm / 72; + m->defaultfontsize = defaultrelfontsize * px_pt; + + // Override defaultfontsize (dpi) by user config + char *name = XGetAtomName(xw.dpy, xm->name); + for (int j = 0; j < LEN(monitors_config); ++j) + if (!strcmp(name, monitors_config[j].name)) { + m->defaultfontsize = monitors_config[j].defaultfontsize; + if (m->defaultfontsize < 0) + m->defaultfontsize *= -px_pt; + break; + } + // fprintf(stderr, "%s: %fpx, %f\n", name, m->defaultfontsize, m->usedfontsize); + XFree(name); + + // Restore usedfontsize (zoom) after re-cache for monitors with the same name + m->usedfontsize = m->defaultfontsize; + for (int j = 0; j < prev_num; ++j) + if (prev_info[j].name == m->name) { + m->usedfontsize = prev_info[j].usedfontsize; + break; + } + } + + XRRFreeMonitors(xmonitors); + free(prev_info); +} + +static int +getmonitorindex_threshold(int w, int h, int x, int y) +{ + int mindex = -1; + float fontsize = 0; + int thresholdarea = winmovethreshold * w * h; + + for (int i = 0; i < monitors_num; ++i) { + MonitorInfo *m = &monitors_info[i]; + int overlap_w = MAX(0, MIN(x + w, m->x + m->w) - MAX(x, m->x)); + int overlap_h = MAX(0, MIN(y + h, m->y + m->h) - MAX(y, m->y)); + int area = overlap_w * overlap_h; + // Choose monitor with largest dpi (defaultfontsize) + // from all "mirrored"/overlapped (e.g. projector) + if (area >= thresholdarea && fontsize < m->defaultfontsize) { + fontsize = m->defaultfontsize; + mindex = i; + } + } + return mindex; +} + +static int +getmonitorindex_nearest(int w, int h, int x, int y) +{ + int mindex = -1; + float fontsize = 0; + int overlaparea = 0; + + for (int i = 0; i < monitors_num; ++i) { + MonitorInfo *m = &monitors_info[i]; + int overlap_w = MAX(0, MIN(x + w, m->x + m->w) - MAX(x, m->x)); + int overlap_h = MAX(0, MIN(y + h, m->y + m->h) - MAX(y, m->y)); + int area = overlap_w * overlap_h; + // Choose monitor with largest overlapping area + // e.g. when "st" is initially spawned in-between monitors + if (area > overlaparea) { + overlaparea = area; + mindex = i; + } + } + return mindex; +} + +static void +adjustmonitorfontsize(int mindex) +{ + if (mindex < 0 || prev_mindex == mindex) + return; + // Save zoom of current monitor before switching + if (prev_mindex >= 0) + monitors_info[prev_mindex].usedfontsize = usedfontsize; + + defaultfontsize = monitors_info[mindex].defaultfontsize; + // fprintf(stderr, "Crossing: %fpx\n", defaultfontsize); + + // NOTE: do nothing if font size differs by less than 1% + double fontsize = monitors_info[mindex].usedfontsize; + double delta = 0.01 * usedfontsize; + if (!BETWEEN(fontsize - usedfontsize, -delta, delta)) { + // fprintf(stderr, "Adjusted: %fpx\n", fontsize); + xunloadfonts(); + xloadfonts(usedfont, fontsize); + } + prev_mindex = mindex; +} + +void +refreshxrandr(const Arg *dummy) +{ + // Reset index to detect change of window association on "xrandr ... --primary" + // otherwise: zoom won't be saved on switching and new font size won't be loaded + // CRIT!!! event from xrandr may place another monitor into same index + if (prev_mindex >= 0) + monitors_info[prev_mindex].usedfontsize = usedfontsize; + prev_mindex = -1; + + XWindowAttributes xattr = {0}; + cachemonitorinfo(); + XGetWindowAttributes(xw.dpy, xw.win, &xattr); + + int mindex = getmonitorindex_threshold(xattr.width, xattr.height, xattr.x, xattr.y); + if (mindex < 0) + mindex = getmonitorindex_nearest(xattr.width, xattr.height, xattr.x, xattr.y); + adjustmonitorfontsize(mindex); +} + + void xbell(void) { @@ -2578,6 +2735,14 @@ cmessage(XEvent *e) void resize(XEvent *e) { + // BAD: no resize on monitor plug/unplug/reconfigure -- until window itself is kept in the same place + // NOTE: no resize event on zoomabs() + // fprintf(stderr, "Resize: %dx%d+%d+%d\n", + // e->xconfigure.width, e->xconfigure.height, e->xconfigure.x, e->xconfigure.y); + + adjustmonitorfontsize(getmonitorindex_threshold( + e->xconfigure.width, e->xconfigure.height, e->xconfigure.x, e->xconfigure.y)); + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) return; @@ -2613,6 +2778,22 @@ run(void) } } while (ev.type != MapNotify); + int rr_event_base, rr_error_base, rr_major, rr_minor; + if (!XRRQueryExtension (xw.dpy, &rr_event_base, &rr_error_base) || + !XRRQueryVersion (xw.dpy, &rr_major, &rr_minor) || + rr_major < 1 || (rr_major == 1 && rr_minor < 5)) + { + die("RandR 1.5 extension isn't available\n"); + } + XRRSelectInput(xw.dpy, xw.win, RRCrtcChangeNotifyMask); + + // WARN: can query actual window size/pos only after window is mapped and its width/height are adjusted by WM + // * x/y are WM-dependent and can't be determined beforehand anyway + // * defaultfontsize isn't available until font is loaded and actual Fc*() size queried + // BAD: fonts on startup are always reloaded -- how to specify their size beforehand ? + FcPatternGetDouble(dc.font.match->pattern, FC_SIZE, 0, &defaultrelfontsize); + refreshxrandr(0); + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); cresize(w, h); @@ -2645,6 +2826,16 @@ run(void) XNextEvent(xw.dpy, &ev); if (XFilterEvent(&ev, None)) continue; + if (LASTEvent <= ev.type) { + if (rr_event_base + RRNotify == ev.type && + RRNotify_CrtcChange == ((XRRNotifyEvent *)&ev)->subtype) + { + XRRUpdateConfiguration(&ev); + // fprintf(stderr, "Monitor change: %d > %d\n", rr_event_base, LASTEvent); + refreshxrandr(0); + } + continue; + } if (handler[ev.type]) (handler[ev.type])(&ev); }