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