1 /* vim:set ts=4 sw=4 et:
2 *
3 * main.c * xrootconsole
4 * Original version: Copyright (C) 1998, 1999 Eric Youngblut
5 * Current version: Copyright (C) 1999 Eric Youngblut & Bob Galloway
6 *
7 * (The transparency stuff was inspired by -- pulled from the still-warm
8 * body of -- wterm. Many thanks to wterm's "anonymous coder!")
9 *
10 * $Id: main.c,v 1.13 2004/02/20 22:31:53 bob Exp $
11 *
12 *
13 *
14 *
15 **************************************************************************
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 *
30 * The author of the original version of this software can be reached
31 * by e-mail at yngblut@cs.washington.edu. The original version of
32 * this software can be found at http://www.cs.washington.edu/homes/yngblut/
33 *
34 * The author of the current version of this software can be reached
35 * by e-mail at bgallowa@wso.williams.edu. The latest version of this
36 * software can be found at http://de-fac.to/bob/xrootconsole/
37 **************************************************************************/
38
39
40 #include "util.h"
41 #include <X11/Xlib.h>
42 #include <X11/Xutil.h>
43 #include <X11/Xos.h>
44 #include <X11/Xatom.h>
45 #include <assert.h>
46 #include <errno.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <fcntl.h>
52
53 #ifndef XROOTCONSOLE_VERSION
54 #define XROOTCONSOLE_VERSION "undefined"
55 #endif
56
57 #define USAGE \
58 "Usage: xrootconsole [options] [console]\n" \
59 "Scroll the console to a transparent window placed on the X root window.\n" \
60 "\n" \
61 " -geometry GEO the geometry of the window (default 80x10+0+0)\n" \
62 " -fn FONT the font to use (default fixed)\n" \
63 " -fg COLOR foreground color of the text (default white)\n" \
64 " -bg COLOR background AND-mask for shaded transparency (default clear)\n" \
65 " -bd COLOR border color (default white)\n" \
66 " -bw WIDTH border width (default 0)\n" \
67 " -c COLUMNS split window into number of text columns (default 1)\n" \
68 " --solid make background a solid color, not shaded-transparent\n" \
69 " --topdown insert lines at the top and scroll the rest down\n" \
70 " --wrap wrap long lines, instead of cutting them off\n" \
71 " -h or --help a familiar-looking help screen\n" \
72 " -v or --version displays the version number\n" \
73 " [console] filename to read (defaults to standard input)\n" \
74 "\n" \
75 "Report bugs to <bob@de-fac.to>.\n"
76
77
78 #define DEFAULT_X 0
79 #define DEFAULT_Y 0
80 #define DEFAULT_COLS 80
81 #define DEFAULT_ROWS 10
82 #define DEFAULT_FONT_NAME "fixed"
83 #define DEFAULT_FOREGROUND_COLOR "white"
84 #define DEFAULT_BACKGROUND_COLOR "white"
85 #define DEFAULT_BORDER_COLOR "white"
86 #define DEFAULT_BORDER_WIDTH 0
87 #define DEFAULT_TEXT_COLUMNS 1
88 #define DEFAULT_WRAP False
89 #define DEFAULT_TOPDOWN False
90 #define DEFAULT_SOLID False
91
92
93 typedef struct {
94 const char* fg;
95 const char* bg;
96 const char* bd;
97 const char* geometry;
98 const char* console_name;
99 const char* font_name;
100 int bw;
101 int tc; /* number of text columns (NOT characters) */
102 int wrap;
103 int topdown;
104 int solid;
105 } InitOptions;
106
107 typedef struct {
108 int x_root; /* position of the window (in pixels) */
109 int y_root;
110 int width; /* size of the window (in pixels) */
111 int height;
112 unsigned int cols; /* size of the window (in characters) */
113 unsigned int rows;
114 int geometry;
115 Display* dpy;
116 Window win;
117 Pixmap buf; /* the main drawable, copied to win by exposures */
118 Pixmap bkg; /* the background pixmap, copied to buf before each drawing */
119 XFontStruct* fn; /* text font */
120 GC foregc;
121 GC backgc;
122 int colwidth; /* width of a column in characters */
123 int nlines; /* number of lines to keep in memory */
124 int intercolgap; /* width between two columns */
125 XSetWindowAttributes swa;
126 } WindowSettings;
127
128 typedef struct {
129 const char* console; /* name of the console file */
130 int fd; /* file descriptor of the console file */
131 char* text; /* circular buffer to store the characters */
132 int pos; /* where the actual first line in text is */
133 } ConsoleSettings;
134
135
136 /*
137 * put
138 */
139
put(const char * s,InitOptions * io,WindowSettings * ws,ConsoleSettings * cs)140 void put(const char* s, InitOptions *io,
141 WindowSettings *ws, ConsoleSettings *cs) {
142 static int x = 0, y = 0;
143
144 while (*s != '\0') {
145 switch (*s) {
146 case '\n':
147 x = 0;
148 if (io->topdown) {
149 cs->pos = (cs->pos + ws->nlines - 1) % ws->nlines;
150 memset(cs->text + cs->pos * ws->colwidth, ' ', ws->colwidth);
151 }
152 else if (y == ws->nlines - 1) {
153 memset(cs->text + cs->pos * ws->colwidth, ' ', ws->colwidth);
154 cs->pos = (cs->pos + 1) % ws->nlines;
155 }
156 else y++;
157 break;
158
159 case '\t':
160 x = (x / 8 + 1) * 8;
161 if (x >= ws->cols && io->wrap) put("\n",io,ws,cs);
162 break;
163
164 default:
165 if (*s >= ' ' && x < ws->colwidth) {
166 cs->text[x + ((y + cs->pos) % ws->nlines) * ws->colwidth] = *s;
167 if (++x == ws->colwidth && io->wrap) put("\n",io,ws,cs);
168 }
169 break;
170 }
171
172 s++;
173 }
174 }
175
176
177 /*
178 * write the text in the pixmap
179 */
draw_pixmap(InitOptions * io,WindowSettings * ws,ConsoleSettings * cs)180 void draw_pixmap(InitOptions *io, WindowSettings *ws, ConsoleSettings *cs) {
181 int rowc = cs->pos + (io->topdown==True); /* row in caracters */
182 int rowp = ws->fn->ascent; /* row in pixels */
183 int i,j;
184
185 /* setup the background */
186 XCopyArea(ws->dpy, ws->bkg, ws->buf, ws->foregc, 0, 0,
187 ws->width, ws->height, 0, 0);
188
189 /* write the string */
190 for (i = ws->rows; i>0; --i) {
191 for (j = 0; j < io->tc; ++j) {
192 XDrawString(ws->dpy, ws->buf, ws->foregc,
193 j * (ws->colwidth * ws->fn->max_bounds.width + ws->intercolgap),
194 rowp,
195 cs->text + ((rowc + j*(ws->rows)) % ws->nlines)*(ws->colwidth),
196 ws->colwidth);
197 }
198 rowp += ws->fn->ascent + ws->fn->descent;
199 ++rowc;
200 }
201 }
202
203
204 /*
205 * init
206 */
207
208
init_options(char ** argv,InitOptions * io)209 void init_options(char **argv, InitOptions *io) {
210 /* initialize InitOptions struct*/
211 io->console_name = NULL;
212 io->geometry = NULL;
213 io->fg = DEFAULT_FOREGROUND_COLOR;
214 io->bg = DEFAULT_BACKGROUND_COLOR;
215 io->bd = DEFAULT_BORDER_COLOR;
216 io->bw = DEFAULT_BORDER_WIDTH;
217 io->tc = DEFAULT_TEXT_COLUMNS;
218 io->font_name = DEFAULT_FONT_NAME;
219 io->wrap = DEFAULT_WRAP;
220 io->topdown = DEFAULT_TOPDOWN;
221 io->solid = DEFAULT_SOLID;
222
223 /* Process command-line arguments */
224 while (*++argv != NULL) {
225 if (!strcmp(*argv, "-geometry") || !strcmp(*argv, "-geo"))
226 io->geometry = *++argv;
227 else if (!strcmp(*argv, "-font") || !strcmp(*argv, "-fn"))
228 io->font_name = *++argv;
229 else if (!strcmp(*argv, "-foreground") || !strcmp(*argv, "-fg"))
230 io->fg = *++argv;
231 else if (!strcmp(*argv, "-background") || !strcmp(*argv, "-bg"))
232 io->bg = *++argv;
233 else if (!strcmp(*argv, "-bordercolor") || !strcmp(*argv, "-bd"))
234 io->bd = *++argv;
235 else if (!strcmp(*argv, "-borderwidth") || !strcmp(*argv, "-bw"))
236 io->bw = atoi(*++argv);
237 else if (!strcmp(*argv, "-columns") || !strcmp(*argv, "-c"))
238 io->tc = atoi(*++argv);
239 else if (!strcmp(*argv, "--solid"))
240 io->solid = True;
241 else if (!strcmp(*argv, "--wrap"))
242 io->wrap = True;
243 else if (!strcmp(*argv, "--topdown"))
244 io->topdown = True;
245 else if (!strcmp(*argv, "--version") || !strcmp(*argv, "-v")) {
246 fprintf(stderr, "xrootconsole %s\n", XROOTCONSOLE_VERSION);
247 exit(EXIT_SUCCESS);
248 }
249 else if (!strcmp(*argv, "--help") || !strcmp(*argv, "-h")) {
250 fprintf(stderr, "xrootconsole %s\n%s", XROOTCONSOLE_VERSION,USAGE);
251 exit(EXIT_SUCCESS);
252 }
253 else {
254 io->console_name = *argv;
255 }
256 /* sanity check! */
257 if (io->tc < 1) io->tc = DEFAULT_TEXT_COLUMNS;
258 }
259 }
260
open_console(const char * console)261 int open_console(const char* console) {
262 int fd;
263 assert(console!=NULL);
264 fd = open(console, O_RDONLY|O_NONBLOCK);
265 if (fd < 0) {
266 fprintf(stderr,"Console %s can't be opened! ",console);
267 perror("Error");
268 exit (EXIT_FAILURE);
269 }
270 return fd;
271 }
272
reset_console(ConsoleSettings * cs)273 void reset_console(ConsoleSettings *cs) {
274 assert(cs->fd >= 0);
275 assert((cs->fd >= 1) || (cs->console == NULL));
276 if (cs->console == NULL) exit(EXIT_SUCCESS);
277 close(cs->fd);
278 cs->fd = -1;
279 }
280
init_console(const char * console,int colwidth,int nlines,ConsoleSettings * cs)281 void init_console(const char *console,
282 int colwidth, int nlines,
283 ConsoleSettings *cs) {
284
285 /* we never modify the name through a ConsoleSettings*,
286 so don't bother copying the string */
287 cs->console = console;
288
289 /* Create the text array. We can display "nlines-1" # of rows, and the
290 extra one is for the line being read. */
291 cs->text = (char*)malloc(colwidth * nlines);
292 memset(cs->text, ' ', colwidth * nlines);
293 cs->pos = 0;
294 if (cs->console == NULL) cs->fd = 0; /* STDIN special case, */
295 else cs->fd = open_console(cs->console); /* or open normal file */
296 }
297
298
299 /* Return the root window, or a virtual root window if any. */
root_window(Display * display)300 static Window root_window(Display *display) {
301 Atom __SWM_VROOT = XInternAtom(display, "__SWM_VROOT", False);
302
303 if (__SWM_VROOT != None) {
304 Window unused, *windows;
305 unsigned int count;
306
307 if (XQueryTree(display, DefaultRootWindow (display), &unused, &unused,
308 &windows, &count)) {
309 int i;
310
311 for (i=0; i<count; i++) {
312 Atom type;
313 int format;
314 unsigned long nitems, bytes_after_return;
315 Window *virtual_root_window;
316
317 if (XGetWindowProperty(display, windows[i], __SWM_VROOT,
318 0, 1, False, XA_WINDOW, &type, &format,
319 &nitems, &bytes_after_return,
320 (unsigned char **) &virtual_root_window) == Success){
321 if (type != None) {
322 if (type == XA_WINDOW) {
323 XFree(windows);
324 return *virtual_root_window;
325 } else {
326 fprintf(stderr,
327 "__SWM_VROOT property type mismatch");
328 }
329 }
330 } else {
331 fprintf(stderr,
332 "failed to get __SWM_VROOT property on window 0x%lx",
333 windows[i]);
334 }
335 }
336
337 if (count)
338 XFree(windows);
339 } else {
340 fprintf(stderr, "Can't query tree on root window 0x%lx",
341 DefaultRootWindow (display));
342 }
343 } else {
344 /* This shouldn't happen. The Xlib documentation is wrong BTW. */
345 fprintf (stderr, "Can't intern atom __SWM_VROOT");
346 }
347
348 return DefaultRootWindow (display);
349 }
350
351
init_window(InitOptions * io,WindowSettings * ws)352 void init_window(InitOptions *io, WindowSettings *ws) {
353 XGCValues values;
354
355 memset(ws,0,sizeof(*ws));
356 ws->x_root = DEFAULT_X;
357 ws->y_root = DEFAULT_Y;
358 ws->cols = DEFAULT_COLS;
359 ws->rows = DEFAULT_ROWS;
360 ws->intercolgap = 0;
361
362 if (io->geometry) {
363 ws->geometry = XParseGeometry(io->geometry,
364 &(ws->x_root), &(ws->y_root), &(ws->cols), &(ws->rows));
365 }
366
367 /* Connect to the X server */
368 ws->dpy = XOpenDisplay(NULL);
369 if (ws->dpy == NULL) {
370 fprintf(stderr, "Cannot open display\n");
371 exit(EXIT_FAILURE);
372 }
373
374 ws->fn = load_font(io->font_name, ws->dpy);
375
376 /* compute text column geometry */
377 ws->nlines = ws->rows * io->tc + 1;
378 ws->colwidth = ws->cols / io->tc;
379 if (io->tc > 1) {
380 /* ensure at least a 1 char gap between columns */
381 if ((ws->cols - ws->colwidth*io->tc)/(io->tc-1) < 1) --(ws->colwidth);
382 ws->intercolgap = (ws->cols - ws->colwidth*io->tc) *
383 ws->fn->max_bounds.width / (io->tc-1);
384 }
385
386 /* Calculate the position of window on the screen */
387 ws->width = ws->cols * ws->fn->max_bounds.width;
388 ws->height = ws->rows * (ws->fn->ascent + ws->fn->descent);
389 ws->x_root = (ws->geometry & XNegative) ?
390 (DisplayWidth(ws->dpy, DefaultScreen(ws->dpy)) -
391 ws->width + ws->x_root - (2*io->bw)) :
392 ws->x_root;
393 ws->y_root = (ws->geometry & YNegative) ?
394 (DisplayHeight(ws->dpy, DefaultScreen(ws->dpy)) -
395 ws->height + ws->y_root - (2*io->bw)) :
396 ws->y_root;
397
398
399 ws->swa.background_pixmap = ParentRelative;
400 ws->swa.border_pixel = load_color(io->bd, ws->dpy);
401 ws->swa.event_mask = ExposureMask;
402 ws->swa.override_redirect = True;
403
404 ws->win = XCreateWindow(ws->dpy, root_window(ws->dpy),
405 ws->x_root, ws->y_root,
406 ws->width, ws->height, io->bw, CopyFromParent, CopyFromParent,
407 DefaultVisual(ws->dpy, DefaultScreen(ws->dpy)),
408 CWOverrideRedirect | CWBackPixmap | CWEventMask |
409 CWBorderPixel, &(ws->swa));
410
411 XMapWindow(ws->dpy, ws->win);
412
413 /* Create the pixmaps */
414 ws->bkg = XCreatePixmap(ws->dpy, ws->win, ws->width, ws->height,
415 DefaultDepth(ws->dpy, DefaultScreen(ws->dpy)));
416 ws->buf = XCreatePixmap(ws->dpy, ws->win, ws->width, ws->height,
417 DefaultDepth(ws->dpy, DefaultScreen(ws->dpy)));
418
419 /* Create the foreground GC */
420 values.font = ws->fn->fid;
421 values.foreground = load_color(io->fg, ws->dpy);
422 values.graphics_exposures = 0;
423 ws->foregc = XCreateGC(ws->dpy, ws->win,
424 GCFont | GCForeground | GCGraphicsExposures,
425 &values);
426
427 /* initialize the background pixmap */
428 values.background = values.foreground;
429 values.foreground = load_color(io->bg, ws->dpy);
430 if (io->solid == True) {
431
432 XLowerWindow(ws->dpy, ws->win);
433 ws->backgc = XCreateGC(ws->dpy, ws->win,
434 GCForeground | GCBackground, &values);
435 XFillRectangle(ws->dpy, ws->bkg, ws->backgc, 0, 0,
436 ws->width, ws->height);
437
438 } else { /* shaded/transparent background */
439
440 values.function = GXand;
441 ws->backgc = XCreateGC(ws->dpy, ws->win,
442 GCForeground | GCBackground | GCFunction,
443 &values);
444
445 /* fill the window with root pixmap */
446 XClearWindow(ws->dpy,ws->win);
447
448 /* take a snapshot for the root pixmap */
449 XCopyArea(ws->dpy, ws->win, ws->bkg, ws->foregc, 0, 0,
450 ws->width, ws->height, 0, 0);
451
452 /* in order to get the full pixmap we have to wait until the pixmap
453 is copied before lowering the window */
454 XLowerWindow(ws->dpy, ws->win);
455
456 /* AND-shade the background */
457 XFillRectangle(ws->dpy, ws->bkg, ws->backgc, 0, 0,
458 ws->width, ws->height);
459 }
460
461 /* prevent the server from redrawing the background */
462 ws->swa.background_pixmap = None;
463 XChangeWindowAttributes(ws->dpy, ws->win, CWBackPixmap, &(ws->swa));
464 }
465
466
467 /*
468 * handle_expose
469 */
470
handle_expose(XExposeEvent * ev,WindowSettings * ws)471 void handle_expose(XExposeEvent* ev, WindowSettings *ws) {
472 XCopyArea(ws->dpy, ws->buf, ws->win, ws->foregc,
473 ev->x, ev->y, ev->width, ev->height,
474 ev->x, ev->y );
475 }
476
477
478
479
480
481 /*
482 * event_loop
483 */
484
event_loop(InitOptions * io,WindowSettings * ws,ConsoleSettings * cs)485 void event_loop(InitOptions *io, WindowSettings *ws, ConsoleSettings *cs) {
486 fd_set rfds;
487 fd_set efds;
488 int Xfd = ConnectionNumber(ws->dpy);
489 int maxfd;
490 struct timeval select_timeout;
491 XEvent ev;
492
493 maxfd = (cs->fd > Xfd ? cs->fd : Xfd) + 1;
494
495 while (1) {
496
497 while ((XPending(ws->dpy) > 0) &&
498 (XCheckMaskEvent(ws->dpy, ExposureMask, &ev) == True)) {
499 handle_expose((XExposeEvent*)&ev.xexpose, ws);
500 }
501
502 XLowerWindow(ws->dpy, ws->win);
503
504 /* has the file moved from under us? re-open if so. */
505 if (cs->fd < 0) {
506 cs->fd = open_console(cs->console);
507 maxfd = (cs->fd > Xfd ? cs->fd : Xfd) + 1;
508 }
509
510 FD_ZERO(&rfds);
511 FD_ZERO(&efds);
512 FD_SET(Xfd, &rfds);
513 FD_SET(cs->fd, &rfds);
514 FD_SET(cs->fd, &efds);
515 select_timeout.tv_sec = 5;
516 select_timeout.tv_usec = 0;
517 if (select(maxfd, &rfds, NULL, &efds, &select_timeout) == -1) {
518 perror("select error");
519 exit(EXIT_FAILURE);
520 }
521
522 if (FD_ISSET(cs->fd, &rfds)) {
523 char buf[1024];
524 int n;
525
526 n = read(cs->fd, buf, sizeof(buf) - 1);
527 if (n == -1) {
528 sleep(1);
529 if (errno != EAGAIN) {reset_console(cs);}
530 } else if (n > 0) {
531 buf[n] = '\0';
532 put(buf,io,ws,cs);
533 draw_pixmap(io,ws,cs);
534 /* make an exposure event */
535 XClearArea(ws->dpy, ws->win, 0, 0, 0, 0, True);
536 } else { /* n == 0 --> EOF */
537 sleep(1);
538 }
539 }
540
541 else if (FD_ISSET(cs->fd, &efds)) {
542 reset_console(cs);
543 sleep(1);
544 }
545
546 } /* end loop */
547 }
548
549 /*
550 * main
551 */
552
main(int argc,char ** argv)553 int main(int argc, char** argv) {
554 InitOptions io;
555 ConsoleSettings cs;
556 WindowSettings ws;
557
558 init_options(argv, &io);
559 init_window(&io,&ws);
560 init_console(io.console_name, ws.colwidth, ws.nlines, &cs);
561
562 /* Display a message */
563 put("xrootconsole ",&io,&ws,&cs);
564 put(XROOTCONSOLE_VERSION,&io,&ws,&cs);
565 put("\n",&io,&ws,&cs);
566
567 draw_pixmap(&io,&ws,&cs);
568
569 event_loop(&io,&ws,&cs);
570
571 XCloseDisplay(ws.dpy);
572 exit(EXIT_SUCCESS);
573 }
574