quake-style console with xterm
at master 381 lines 9.1 kB view raw
1/* 2 * qconsole 3 * Copyright (c) 2005-2017 joshua stein <jcs@jcs.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include <err.h> 30#include <getopt.h> 31#include <signal.h> 32#include <stdio.h> 33#include <stdarg.h> 34#include <stdlib.h> 35#include <string.h> 36#include <time.h> 37#include <unistd.h> 38#include <sys/types.h> 39#include <sys/wait.h> 40 41#include <X11/Xlib.h> 42#include <X11/Xutil.h> 43#include <X11/Xproto.h> 44 45#define DIR_UP 1 46#define DIR_DOWN -1 47 48#define MAX_SPEED 10 49#define DEF_SPEED (MAX_SPEED / 2) 50 51#define BORDER 4 52 53#define WIN_NAME "qconsole" 54 55struct xinfo { 56 Display* dpy; 57 Window win; 58 Window xterm; 59 int dpy_width, dpy_height; 60 int screen; 61 int speed; 62 int width, height; 63 int cur_direction; 64} main_win; 65 66/* args to pass to xterm; requires a trailing blank -into option */ 67static char *xterm_args[6] = { 68 "xterm", 69 "-name", WIN_NAME, 70 "-into", "", 71 NULL 72}; 73 74static int debug = 0; 75#define DPRINTF(x) { if (debug) { printf x; } }; 76 77static pid_t xterm_pid = 0; 78static int shutting_down = 0; 79static int respawning = 0; 80 81extern char *__progname; 82 83void draw_window(const char *); 84void scroll(int direction, int quick); 85void xterm_spawn(void); 86void child_handler(int sig); 87void exit_handler(int sig); 88void x_error_handler(Display * d, XErrorEvent * e); 89void usage(void); 90 91int 92main(int argc, char* argv[]) 93{ 94 char *display = NULL, *p; 95 int ch; 96 97 memset(&main_win, 0, sizeof(struct xinfo)); 98 main_win.speed = DEF_SPEED; 99 main_win.cur_direction = DIR_UP; 100 101 /* will get set according to screen height if not overridden by -h */ 102 main_win.height = 0; 103 104 while ((ch = getopt(argc, argv, "dh:s:")) != -1) 105 switch (ch) { 106 case 'd': 107 debug = 1; 108 break; 109 110 case 'h': 111 main_win.height = strtol(optarg, &p, 10); 112 if (*p || main_win.height < 1) 113 errx(1, "illegal height value -- %s", optarg); 114 115 break; 116 117 case 's': 118 main_win.speed = strtol(optarg, &p, 10); 119 if (*p || main_win.speed < 1 || 120 main_win.speed > MAX_SPEED) 121 errx(1, "speed must be between 1 and %d", 122 MAX_SPEED); 123 124 break; 125 126 default: 127 usage(); 128 } 129 130 argc -= optind; 131 argv += optind; 132 133 /* die gracefully */ 134 signal(SIGINT, exit_handler); 135 signal(SIGTERM, exit_handler); 136 137 /* handle xterm exiting */ 138 signal(SIGCHLD, child_handler); 139 140 /* fire up initial xterm */ 141 draw_window(display); 142 xterm_spawn(); 143 144 /* wait for events */ 145 for (;;) { 146 XEvent event; 147 memset(&event, 0, sizeof(XEvent)); 148 XNextEvent(main_win.dpy, &event); 149 150 switch (event.type) { 151 case ReparentNotify: { 152 DPRINTF(("xterm spawned and reparented to us\n")); 153 154 XReparentEvent *e = (XReparentEvent *) &event; 155 main_win.xterm = e->window; 156 157 /* move completely off-screen */ 158 XMoveWindow(main_win.dpy, main_win.xterm, -1, -1); 159 XResizeWindow(main_win.dpy, main_win.xterm, 160 main_win.width, main_win.height - BORDER); 161 162 break; 163 } 164 165 case UnmapNotify: 166 DPRINTF(("xterm unmapped, respawning\n")); 167 168 xterm_spawn(); 169 170 break; 171 172 case KeyRelease: 173 scroll(-(main_win.cur_direction), 0); 174 break; 175 176 case FocusOut: 177 if (main_win.cur_direction == DIR_DOWN) 178 XSetInputFocus(main_win.dpy, main_win.xterm, 179 RevertToParent, CurrentTime); 180 181 break; 182 } 183 } 184 185 return (0); 186} 187 188void 189draw_window(const char *display) 190{ 191 int rc; 192 XSetWindowAttributes attributes; 193 XTextProperty win_name_prop; 194 char *win_name = WIN_NAME; 195 196 if (!(main_win.dpy = XOpenDisplay(display))) 197 errx(1, "Unable to open display %s", XDisplayName(display)); 198 199 XSetErrorHandler ((XErrorHandler) x_error_handler); 200 201 if (main_win.height == 0) 202 main_win.height = DisplayHeight(main_win.dpy, 203 main_win.screen) / 5; 204 205 main_win.screen = DefaultScreen(main_win.dpy); 206 main_win.width = main_win.dpy_width = DisplayWidth(main_win.dpy, 207 main_win.screen); 208 main_win.dpy_height = DisplayHeight(main_win.dpy, main_win.screen); 209 main_win.win = XCreateSimpleWindow(main_win.dpy, 210 RootWindow(main_win.dpy, main_win.screen), 211 0, -(main_win.height), 212 main_win.width, main_win.height, 213 0, 214 BlackPixel(main_win.dpy, main_win.screen), 215 BlackPixel(main_win.dpy, main_win.screen)); 216 217 if (!(rc = XStringListToTextProperty(&win_name, 1, &win_name_prop))) 218 errx(1, "XStringListToTextProperty"); 219 220 XSetWMName(main_win.dpy, main_win.win, &win_name_prop); 221 222 /* remove all window manager decorations and force our position/size */ 223 attributes.override_redirect = True; 224 XChangeWindowAttributes(main_win.dpy, main_win.win, CWOverrideRedirect, 225 &attributes); 226 227 XMapRaised(main_win.dpy, main_win.win); 228 229 XSync(main_win.dpy, False); 230 231 /* we need to know when the xterm gets reparented to us */ 232 XSelectInput(main_win.dpy, main_win.win, SubstructureNotifyMask | 233 FocusChangeMask); 234 235 /* bind to control+o */ 236 /* TODO: allow this key to be configurable */ 237 XGrabKey(main_win.dpy, XKeysymToKeycode(main_win.dpy, XK_o), 238 ControlMask, DefaultRootWindow(main_win.dpy), False, GrabModeAsync, 239 GrabModeAsync); 240} 241 242void 243scroll(int direction, int quick) 244{ 245 int cur_x, cur_y, inc, dest; 246 unsigned width, height, bw, depth; 247 Window root; 248 249 if (direction == DIR_DOWN) { 250 XSetInputFocus(main_win.dpy, main_win.xterm, RevertToParent, 251 CurrentTime); 252 dest = 0; 253 } else { 254 XSetInputFocus(main_win.dpy, PointerRoot, RevertToParent, 255 CurrentTime); 256 dest = -(main_win.height); 257 } 258 259 XGetGeometry(main_win.dpy, main_win.win, &root, &cur_x, &cur_y, &width, 260 &height, &bw, &depth); 261 262 DPRINTF(("scrolling from %d to %d%s\n", cur_y, dest, 263 (quick ? " quickly" : ""))); 264 265 if (direction == DIR_DOWN) 266 XRaiseWindow(main_win.dpy, main_win.win); 267 268 /* smoothly scroll to our destination */ 269 while (!quick && cur_y != dest) { 270 inc = (abs(dest - cur_y) / MAX_SPEED) / (MAX_SPEED + 1 - 271 main_win.speed); 272 273 if (inc < 1) 274 inc = 1; 275 276 if ((direction == DIR_DOWN && (cur_y + inc >= dest)) || 277 (direction == DIR_UP && (cur_y - inc < dest))) 278 break; 279 280 cur_y -= (inc * direction); 281 282 XMoveWindow(main_win.dpy, main_win.win, 0, cur_y); 283 XSync(main_win.dpy, False); 284 } 285 286 XMoveWindow(main_win.dpy, main_win.win, 0, dest); 287 main_win.cur_direction = direction; 288 289 if (direction == DIR_DOWN) 290 XRaiseWindow(main_win.dpy, main_win.win); 291 else 292 XLowerWindow(main_win.dpy, main_win.win); 293} 294 295void 296child_handler(int sig) 297{ 298 if (!xterm_pid) 299 return; 300 301 if (sig) 302 DPRINTF(("got SIGCHLD, cleaning up after xterm pid %d\n", 303 xterm_pid)); 304 305 waitpid(-1, NULL, WNOHANG); 306 xterm_pid = 0; 307} 308 309void 310xterm_spawn(void) 311{ 312 pid_t pid; 313 314 /* this is called in response to SIGCHLD, but signal handlers can't do 315 * any x11 operations */ 316 317 if (shutting_down) { 318 DPRINTF(("shutting down, not respawning\n")); 319 320 return; 321 } 322 323 DPRINTF(("in xterm_spawn\n")); 324 325 /* clean up if previous xterm died */ 326 if (xterm_pid) { 327 child_handler(0); 328 329 scroll(DIR_UP, 1); 330 XUnmapSubwindows(main_win.dpy, main_win.win); 331 } 332 333 if (!xterm_pid && !shutting_down) { 334 DPRINTF(("forking new xterm into %d\n", (int)main_win.win)); 335 336 switch (pid = fork()) { 337 case -1: 338 errx(1, "unable to fork"); 339 340 case 0: 341 /* fork xterm and pass our window id to -into opt */ 342 asprintf(&xterm_args[4], "%d", (int)main_win.win); 343 execvp("xterm", xterm_args); 344 exit(0); 345 346 default: 347 xterm_pid = pid; 348 /* we are now able to be killed */ 349 respawning = 1; 350 } 351 } 352} 353 354void 355exit_handler(int sig) 356{ 357 shutting_down = 1; 358 359 DPRINTF(("in exit_handler with sig %d, shutting down\n", sig)); 360 361 if (xterm_pid) 362 kill(xterm_pid, SIGKILL); 363 364 exit(0); 365} 366 367void 368x_error_handler(Display * d, XErrorEvent * e) 369{ 370 if (e->error_code == BadAccess && e->request_code == X_GrabKey) 371 errx(1, "could not bind key. possibly another application " 372 "bound to it?\n"); 373} 374 375void 376usage(void) 377{ 378 fprintf(stderr, "usage: %s [-d] [-h <height>] [-s <speed 1-%d>]\n", 379 __progname, MAX_SPEED); 380 exit(1); 381}