From bbb499e87a8c905214478e1badd67465701fc5cb Mon Sep 17 00:00:00 2001 From: Ryan McBride Date: Sat, 7 Feb 2009 19:49:58 +0000 Subject: [PATCH] Let scrotwm adjust the font size on xterms as it squishes them in tiling mode to keep the terminal width above a certan size (set with 'term_width' in the config file. We do this by sending the default font size change keystrokes to the xterm in question. Because xterm does not accept 'synthetic' keystroke events by default, and we don't want to force users to enable acceptSendEvents for security reasons, hook XtAppNextEvent in the LD_PRELOAD hack, and clear the send_event flag on the events in question. CAVEAT: Only works if xterm is not setuid/setgid. --- lib/swm_hack.c | 63 +++++++++++++++++++++++++++++++--- scrotwm.1 | 15 ++++++++ scrotwm.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 176 insertions(+), 7 deletions(-) diff --git a/lib/swm_hack.c b/lib/swm_hack.c index 459d880..5d60d61 100644 --- a/lib/swm_hack.c +++ b/lib/swm_hack.c @@ -48,11 +48,15 @@ #include #include #include +#include -/* dlopened xlib so we can find the symbols in the real xlib to call them */ -static void *lib_xlib = NULL; +/* dlopened libs so we can find the symbols in the real one to call them */ +static void *lib_xlib = NULL; +static void *lib_xtlib = NULL; -static Window root = None; +static Window root = None; +static int xterm = 0; +static Display *dpy = NULL; /* Find our root window */ static Window @@ -111,8 +115,10 @@ XCreateWindow(Display * display, Window parent, int x, int y, /* find the real Xlib and the real X function */ if (!lib_xlib) lib_xlib = dlopen("libX11.so", RTLD_GLOBAL | RTLD_LAZY); - if (!func) + if (!func) { func = (CWF *) dlsym(lib_xlib, "XCreateWindow"); + dpy = display; + } if (parent == DefaultRootWindow(display)) parent = MyRoot(display); @@ -125,6 +131,10 @@ XCreateWindow(Display * display, Window parent, int x, int y, set_property(display, id, "_SWM_WS", env); if ((env = getenv("_SWM_PID")) != NULL) set_property(display, id, "_SWM_PID", env); + if ((env = getenv("_SWM_XTERM_FONTADJ")) != NULL) { + unsetenv("_SWM_XTERM_FONTADJ"); + xterm = 1; + } } return (id); } @@ -164,6 +174,10 @@ XCreateSimpleWindow(Display * display, Window parent, int x, int y, set_property(display, id, "_SWM_WS", env); if ((env = getenv("_SWM_PID")) != NULL) set_property(display, id, "_SWM_PID", env); + if ((env = getenv("_SWM_XTERM_FONTADJ")) != NULL) { + unsetenv("_SWM_XTERM_FONTADJ"); + xterm = 1; + } } return (id); } @@ -188,3 +202,44 @@ XReparentWindow(Display * display, Window window, Window parent, int x, int y) return (*func) (display, window, parent, x, y); } + +typedef void (ANEF) (XtAppContext app_context, XEvent *event_return); +int evcount = 0; + +/* + * XtAppNextEvent Intercept Hack + * Normally xterm rejects "synthetic" (XSendEvent) events to prevent spoofing. + * We don't want to disable this completely, it's insecure. But hook here + * and allow these mostly harmless ones that we use to adjust fonts. + */ +void +XtAppNextEvent(XtAppContext app_context, XEvent *event_return) +{ + static ANEF *func = NULL; + static int kp_add = 0, kp_subtract = 0; + + /* find the real Xlib and the real X function */ + if (!lib_xtlib) + lib_xtlib = dlopen("libXt.so", RTLD_GLOBAL | RTLD_LAZY); + if (!func) { + func = (ANEF *) dlsym(lib_xtlib, "XtAppNextEvent"); + if (dpy != NULL) { + kp_add = XKeysymToKeycode(dpy, XK_KP_Add); + kp_subtract = XKeysymToKeycode(dpy, XK_KP_Subtract); + } + } + + (*func) (app_context, event_return); + + /* Return here if it's not an Xterm. */ + if (!xterm) + return; + + /* Allow spoofing of font change keystrokes. */ + if ((event_return->type == KeyPress || + event_return->type == KeyRelease) && + event_return->xkey.state == ShiftMask && + (event_return->xkey.keycode == kp_add || + event_return->xkey.keycode == kp_subtract)) + event_return->xkey.send_event = 0; +} diff --git a/scrotwm.1 b/scrotwm.1 index b133aea..1d21545 100644 --- a/scrotwm.1 +++ b/scrotwm.1 @@ -201,6 +201,21 @@ Set to the script that will take screenshots. It will be called with "full" or "window" as parameter 1 to indicate what screenshot action is expected. The script shall handle those cases accordingly. +.It Cm term_width +Set a preferred minimum width for the terminal +If this value is greater than 0, +.Nm +will attempt to adjust the font sizes in the terminal to keep the terminal +width above this number as the window is resized. +Only +.Xr xterm 1 +is currently supported. +The +.Xr xterm 1 +binary must not be setuid or setgid, which it is by default on most systems. +Users may need to set spawn term to use an alternate copy of the +.Xr xterm 1 +binary without the setgid bit set. .El .Pp Colors need to be specified per the diff --git a/scrotwm.c b/scrotwm.c index 99888d8..835c1d4 100644 --- a/scrotwm.c +++ b/scrotwm.c @@ -132,6 +132,7 @@ u_int32_t swm_debug = 0 #define Y(r) (r)->g.y #define WIDTH(r) (r)->g.w #define HEIGHT(r) (r)->g.h +#define SWM_MAX_FONT_STEPS (3) #ifndef SWM_LIB #define SWM_LIB "/usr/X11R6/lib/swmhack.so" @@ -151,6 +152,8 @@ Display *display; int cycle_empty = 0; int cycle_visible = 0; +int term_width = 0; +int font_adjusted = 0; /* dialog windows */ double dialog_ratio = .6; @@ -222,6 +225,9 @@ struct ws_win { int floating; int transient; int manual; + int font_size_boundary[SWM_MAX_FONT_STEPS]; + int font_steps; + int last_inc; unsigned long quirks; struct workspace *ws; /* always valid */ struct swm_screen *s; /* always valid, never changes */ @@ -335,12 +341,14 @@ struct quirk { #define SWM_Q_FLOAT (1<<0) /* float this window */ #define SWM_Q_TRANSSZ (1<<1) /* transiend window size too small */ #define SWM_Q_ANYWHERE (1<<2) /* don't position this window */ +#define SWM_Q_XTERM_FONTADJ (1<<3) /* adjust xterm fonts when resizing */ } quirks[] = { { "MPlayer", "xv", SWM_Q_FLOAT }, { "OpenOffice.org 2.4", "VCLSalFrame", SWM_Q_FLOAT }, { "OpenOffice.org 3.0", "VCLSalFrame", SWM_Q_FLOAT }, { "Firefox-bin", "firefox-bin", SWM_Q_TRANSSZ}, { "Gimp", "gimp", SWM_Q_FLOAT | SWM_Q_ANYWHERE}, + { "XTerm", "xterm", SWM_Q_XTERM_FONTADJ}, { NULL, NULL, 0}, }; @@ -562,13 +570,23 @@ conf_load(char *filename) case 's': if (!strncmp(var, "spawn_term", strlen("spawn_term"))) asprintf(&spawn_term[0], "%s", val); - if (!strncmp(var, "screenshot_enabled", + else if (!strncmp(var, "screenshot_enabled", strlen("screenshot_enabled"))) ss_enabled = atoi(val); - if (!strncmp(var, "screenshot_app", + else if (!strncmp(var, "screenshot_app", strlen("screenshot_app"))) asprintf(&spawn_screenshot[0], "%s", val); + else + goto bad; break; + + case 't': + if (!strncmp(var, "term_width", strlen("term_width"))) + term_width = atoi(val); + else + goto bad; + break; + default: goto bad; } @@ -850,6 +868,34 @@ unmap_all(void) } void +fake_keypress(struct ws_win *win, int keysym, int modifiers) +{ + XKeyEvent event; + + event.display = display; /* Ignored, but what the hell */ + event.window = win->id; + event.root = win->s->root; + event.subwindow = None; + event.time = CurrentTime; + event.x = win->g.x; + event.y = win->g.y; + event.x_root = 1; + event.y_root = 1; + event.same_screen = True; + event.keycode = XKeysymToKeycode(display, keysym); + event.state = modifiers; + + event.type = KeyPress; + XSendEvent(event.display, event.window, True, + KeyPressMask, (XEvent *)&event); + + event.type = KeyRelease; + XSendEvent(event.display, event.window, True, + KeyPressMask, (XEvent *)&event); + +} + +void restart(struct swm_region *r, union arg *args) { DNPRINTF(SWM_D_MISC, "restart: %s\n", start_argv[0]); @@ -951,6 +997,16 @@ spawn(struct swm_region *r, union arg *args) } void +spawnterm(struct swm_region *r, union arg *args) +{ + DNPRINTF(SWM_D_MISC, "spawnterm\n"); + + if (term_width) + setenv("_SWM_XTERM_FONTADJ", "", 1); + spawn(r, args); +} + +void spawnmenu(struct swm_region *r, union arg *args) { DNPRINTF(SWM_D_MISC, "spawnmenu\n"); @@ -1280,6 +1336,8 @@ stack(void) { r->ws->cur_layout->l_stack(r->ws, &g); } } + if (font_adjusted) + font_adjusted--; XSync(display, False); } @@ -1312,6 +1370,35 @@ stack_floater(struct ws_win *win, struct swm_region *r) XConfigureWindow(display, win->id, mask, &wc); } +/* + * Send keystrokes to terminal to decrease/increase the font size as the + * window size changes. + */ +void +adjust_font(struct ws_win *win) +{ + if (!(win->quirks & SWM_Q_XTERM_FONTADJ) || + win->floating || win->transient) + return; + + if (win->sh.width_inc && win->last_inc != win->sh.width_inc && + win->g.w / win->sh.width_inc < term_width && + win->font_steps < SWM_MAX_FONT_STEPS) { + win->font_size_boundary[win->font_steps] = + (win->sh.width_inc * term_width) + win->sh.base_width; + win->font_steps++; + font_adjusted++; + win->last_inc = win->sh.width_inc; + fake_keypress(win, XK_KP_Subtract, ShiftMask); + } else if (win->font_steps && win->last_inc != win->sh.width_inc && + win->g.w > win->font_size_boundary[win->font_steps - 1]) { + win->font_steps--; + font_adjusted++; + win->last_inc = win->sh.width_inc; + fake_keypress(win, XK_KP_Add, ShiftMask); + } +} + #define SWAPXY(g) do { \ int tmp; \ tmp = (g)->y; (g)->y = (g)->x; (g)->x = tmp; \ @@ -1461,6 +1548,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip) win->g.w = wc.width = win_g.w; win->g.h = wc.height = win_g.h; } + adjust_font(win); mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth; XConfigureWindow(display, win->id, mask, &wc); XMapRaised(display, win->id); @@ -1739,7 +1827,7 @@ struct key { { MODKEY, XK_k, focus, {.id = SWM_ARG_ID_FOCUSPREV} }, { MODKEY | ShiftMask, XK_j, swapwin, {.id = SWM_ARG_ID_SWAPNEXT} }, { MODKEY | ShiftMask, XK_k, swapwin, {.id = SWM_ARG_ID_SWAPPREV} }, - { MODKEY | ShiftMask, XK_Return, spawn, {.argv = spawn_term} }, + { MODKEY | ShiftMask, XK_Return, spawnterm, {.argv = spawn_term} }, { MODKEY, XK_p, spawnmenu, {.argv = spawn_menu} }, { MODKEY | ShiftMask, XK_q, quit, {0} }, { MODKEY, XK_q, restart, {0} }, @@ -2298,8 +2386,19 @@ configurerequest(XEvent *e) void configurenotify(XEvent *e) { + struct ws_win *win; + long mask; + DNPRINTF(SWM_D_EVENT, "configurenotify: window: %lu\n", e->xconfigure.window); + + win = find_window(e->xconfigure.window); + XMapWindow(display, win->id); + XGetWMNormalHints(display, win->id, &win->sh, &mask); + adjust_font(win); + XMapWindow(display, win->id); + if (font_adjusted) + stack(); } void -- 1.7.10.4