root-tail-1.2/0000755000000000000000000000000010102521240013207 5ustar rootroot00000000000000root-tail-1.2/root-tail.man0000644000000000000000000001104610036342315015632 0ustar rootroot00000000000000.TH ROOTTAIL 1 "2004-03-27" "Version 1.0" .UC 5 .SH NAME root-tail \- print text directly to X11 root window .SH SYNOPSIS .B root-tail .RB [ \-g \|| \-\-geometry .IR GEOMETRY ] .RB [ \-fn \|| \-\-font .IR FONTSPEC ] .RB [ \-\-color .IR color ] .RB [ \-\-reload .IR SEC .IR COMMAND ] .RB [ \-\-shade ] .RB [ \-\-outline ] .RB [ \-\-minspace ] .RB [ \-\-noflicker ] .RB [ \-f \|| \-\-fork ] .RB [ \-\-reverse ] .RB [ \-\-whole ] .RB [ \-\-partial ] .RB [ \-\-update ] .RB [ \-\-cont .IR STRING ] .RB [ \-\-wordwrap ] .RB [ \-\-justify ] .RB [ \-\-noinitial ] .RB [ \-\-frame ] .RB [ \-id .IR ID ] .RB [ \-i \|| \-\-interval .IR SECONDS ] .RB [ \-V ] file1[,color[,desc]] [file2[,color[,desc]]] .\" Die Beschreibung .SH DESCRIPTION Displays a given file anywhere on your X11 root window, i.e. it is kind of tail -f for multiple files using your desktop background as output window. .PP All non-option arguments on the comamnd line are files to be logged. A null desc (example: "/var/log/messages,red,") will prevent the printing of a description and the []'s. .PP .SS General Options .TP .BI \-\-color " COLOR" Use COLOR as default. .TP .BI "\-\-font \|| -fn" " FONTSPEC" Use font FONTSPEC. This can be either a fixed width font like \fB\-fn\fI fixed\fR or any font using \fB\-fn \fR'\fI-*-*-*-*-*-*-*-*-*-*-*-*-*-*\fR' with the appropriate fields filled out (see xfontsel). Specifying a different FONTSPEC before each filename will cause each file to be displayed in a different font. .TP .B -f \|| \-\-fork Forks to the background. .TP .B \-\-reverse Display the files in reverse order, with the newest lines at the top. .TP .B \-\-whole Only display whole lines. If the last line of a file doesn't yet end with a newline character then wait until it does before displaying it. This is the default if more than one file is being displayed. .TP .B \-\-partial This is the opposite of the \fB\-\-whole\fR option (see above). It displays lines even if they don't yet have a newline at the end. This is the default if only one file is being displayed. .TP .B \-\-update Update partial lines 'in place' if they are still on the screen when they are updated in their files. Using \fB\-\-update\fR automatically turns on \fB\-\-partial\fR. .TP .BI \-\-cont " STRING" When partial lines are broken into two lines in the display, prefix the 2nd line with STRING. Defaults to "\fI|| \fR". Specify the "\fB\-\-whole\fR" argument to ensure partial lines are never displayed, or specify "\fB\-\-update\fR" to attempt to "repair" broken lines in-place. .TP .BI \-\-cont-color " COLOR" Use COLOR when displaying the continuation string (as optionally specified with the \fB\-\-cont\fR option above). .TP .B \-\-wordwrap The default behaviour is to fit as much as possible onto each line of output, even if this means splitting a word between one line and the next. The \fB\-\-wordwrap\fR argument splits lines at spaces if possible. .TP .B \-\-justify After wrapping long lines, attempt to justify the text to produce a smooth right-hand margin. Implies \fB\-\-wordwrap\fR. .TP .BI \-\-reload " SEC COMMAND" Re-display the file(s) and run COMMAND every SEC seconds. The default is to never re-display the file(s). .TP .B \-\-shade Add black shading to the font. .TP .B \-\-outline Add a black outline to the font (making redraws quite a bit slower). .TP .B \-\-minspace Use minimum linespace even when using shading or outlining. This might result in leftover pixels (dependign on font and logfile content). .TP .B \-\-noflicker Use slower but flicker-free update. .TP .B \-\-noinitial Don't display the end of the file(s) initially. .TP .BI "\-id" " ID" Use the given window ID for output instead of the root window. .TP .BI "\-i \|| \-\-interval" " SECONDS" Use the specified sleeping interval between checks instead of the default 2.4 seconds. Fractional values are OK. .TP .B \-V Print version information. .TP .B \-\-frame Draw a frame around the selected area. This is useful when trying to find the perfect geometry. .IP .SH EXAMPLE .PP root-tail -g 800x250+100+50 -font 10x20 /var/log/messages,green -font 12x24 /var/log/secure,red,'ALERT' .SH BUGS .PP Some desktop environments open a virtual root window and make it difficult to share it. If you cannot see anything after starting root-tail, try to find a setting "allow programs on desktop" or similar, or manually specify a window id. .PP Should you happen to find any bugs please fix them and send me a diff. .PP NOTE: This program was modified by Marc Lehmann , who couldn't reach the original author. Please direct bug-reports etc. to pcg@goof.com. .PP http://root-tail.plan9.de/ root-tail-1.2/config.h0000644000000000000000000000056110102521127014633 0ustar rootroot00000000000000/* root tail compile time defaults */ #define DEF_COLOR "white" #define DEF_CONT_COLOR "grey" //default font.. -font at runtime should work #define USE_FONT "*" //default positions.. can be changed with -g at runtime #define STD_WIDTH 730 #define STD_HEIGHT 530 #define LOC_X 30 #define LOC_Y 30 #define VERSION "1.2" root-tail-1.2/Imakefile0000644000000000000000000000015007535422572015044 0ustar rootroot00000000000000 LOCAL_LIBRARIES = $(XLIB) SRCS = root-tail.c OBJS = $(SRCS:.c=.o) ComplexProgramTarget(root-tail) root-tail-1.2/mkdist0000755000000000000000000000071110033142023014430 0ustar rootroot00000000000000#!/bin/sh VERSION=$(perl -ne 'print $1 if /VERSION \"(.*)\"/' config.h) DISTDIR=root-tail-$VERSION ../fcrackzip/myman2html root-tail.man | tail +3 | tac | tail +7 | tac >/root/www/nh/root-tail.man.nh rm -rf $DISTDIR mkdir $DISTDIR cp -Rlpv Changes Imakefile Makefile.simple README config.h debian mkdist root-tail.c root-tail.man $DISTDIR chown -R root.root $DISTDIR chmod u=rwX,go=rX -R $DISTDIR tar cvf $DISTDIR.tar $DISTDIR gzip -9f $DISTDIR.tar root-tail-1.2/Changes0000644000000000000000000001167410102521200014507 0ustar rootroot000000000000001.2 Fri Jul 30 21:31:42 CEST 2004 - replaced default font by "*" to "ensure" some font is found. - accept hexadecimal window ids, as does everybody else(?). - made it compile with ansi-89-based compilers again. - add hack to support nautilus (not very stable, nautilus should either use extended window manager hints or __SWM_VROOT, as everybody else does). - do not initialize the window twice (fixes problems with multiple visuals). - new option: -minspace. - fixed seg fault if geometry is too short to fit a single line - fix bug which became apparent if -no-filename or null descriptions were used. 1.1 Thu Apr 8 21:59:35 CEST 2004 - make it work with virtual root windows (again). - added options: * -justify: gives a smooth right-hand margin * -cont-color: allows the continuation marker to be a different color than the logfile text - changed the code around quite a lot - it no longer continually deletes lines and re-adds them, and the main structure is now a linked list rather than an array. the continuation marker is added at display time, rather than being in the strings of the datastructure. - it is now possible to specify a different font for each logfile; use the -fn option between filenames. - fix very minor memleak. 1.0 Fri Apr 2 03:37:56 CEST 2004 - fix a bug regarding long (>1023 bytes) lines. - c89 compatibility fixes by Seth W. Klein. - fix a memleak. 0.95 Thu Apr 1 00:39:08 CEST 2004 - -geometry now expects _pixels_ not _characters_. - sped up redraws considerably (depending on xserver). - i18n: locale settings for multibyte characters are respected. - proportional fonts are supported now. - supports options as --long options now. - force initial call to redraw(). - added options: * -whole : to only display lines once they are \n terminated * -partial: to display lines whether they are terminated or not * -update: to attempt to complete old partial lines 'in-place' * -cont: to specify a string to prefix 'broken' partial lines with * -outline: black outline (relatively slow). * -noflicker: slower but flickerfree update. * -wordwrap: wrap at spaces to avoid breaking words - document default interval of 2.4 seconds. - set default 'reload' value to 0 to prevent reloads. - updated man page. - strict-aliasing fix suggested by mmj. - improved regexp transform code, but not enough... - --frame now draws the frame in exactly the right place, just inside the window (previously a geometry of 1024x768+0+0 would have its frame off-screen on my monitor) - the code takes the --shade and --outline options into account properly when wrapping, clearing text, etc. and so no 'debris' pixels are left behind any more. 0.9 Wed Feb 25 15:25:49 CET 2004 - unsigned/fseek bug fix by (mmj@panther.mmj.dk). - patch by Didier Verna (2004-01-07) * root-tail.c (root_window): New function. Return the real root window, or the virtual one if any. (InitWindow): Use it. (force_refresh): Clear the root-tail area, not the whole window. (blank_window): Ditto. (main_loop): Don't forget to redraw () the initial contents. 0.2 Wed Sep 4 17:20:57 CEST 2002 - applied patch by ami@fischman.org (tac). - applied patch by raldi@verisignlabs.com (-noinitial fix). 0.1.1 - backported fixes from Marco d'Itri, who did most of the maintaining work over the last year(s). Unfortunately, he doesn't answer my mails (just as I did to most of root-tails users ;), so this is also the first sourceforge release. - convert tab characters to spaces - fix -noinital bug 0.0.10 - initial lines are now displayed in the correct colour - documentation fixes 0.0.9 Mon Nov 13 13:53:05 CET 2000 - fix descent redraw problem - fix window offset miscalculation - new option -interval - new option -V - more options shortcut - ported to linux 2.4.0, thanks to stefan@weihnachtsmann.at 0.0.8 Sun Nov 12 14:16:04 CET 2000 - small bugfixes. - memory corruption fix. - added fixes by Olexij Tkatchenko. - fixed garbage on top of screen (ashe@sanctuary.org) - added -noinitial option to keep from drawing to screen until new data has arrived (ashe@sanctuary.org) - restores background on quit (ashe@sanctuary.org) - shading now simulates light source coming from top-left instead of bottom-right to fit with every other GUI in the world (ashe@sanctuary.org) - removed startup message. 0.0.7 Tue Oct 10 16:45:17 CEST 2000 - fix "lines containing zero" == endless loop bug. - *ugly* workaround for the "does not redraw all lines correctly" bug. no time to investigate this horror. - fix segv on HUP bug. root-tail-1.2/README0000644000000000000000000000277110032531770014111 0ustar rootroot00000000000000INSTALL xmkmf -a make make install make install.man OPINION In my opinion, this program is a pile of crap which gets crappier with every patch, but it is also sooo useful ;*) MAINTAINER This program has gone through many hands (and is maybe the most-forked-program ever), the current self-appointed maintainer, who never wanted to publish his version is: Marc Lehmann , http://root-tail.plan9.de/ Please send him any patches for this version. AUTHORS Please tell me if I left anybody out. Mike Baker original author Ivo van der Wijk co-author Marc Lehmann _lots_ of bugfixes and improvements Chris Moore _lots_ of bugfixes and improvements Olexij Tkatchenko correct refresh ;) Daniel Lowe many small improvements andre@aleph.it signal handling Mike Schiraldi detabification, -noinital bugfix Mads Martin Joergensen bugfixes Didier Verna gnome/kde integration ami@fischman.org tac raldi@verisignlabs.com bugfixes Marco d'Itri bugfixes Here is a list of all (since I started this list) people that helped by diagnosing a bug: Gunnar Ritter Kevin Bryan Eric Thompson root-tail-1.2/debian/0000755000000000000000000000000010031020163014430 5ustar rootroot00000000000000root-tail-1.2/debian/changelog0000644000000000000000000000572107723212705016332 0ustar rootroot00000000000000root-tail (0.2-1) unstable; urgency=low * New upstream version * Updated to Standards Version 3.6.1 -- Stephen Gran Sun, 10 Aug 2003 15:59:20 -0400 root-tail (0.1.0-4) unstable; urgency=low * New Maintainer * Updated to latest policy -- Stephen Gran Sun, 20 Oct 2002 21:54:41 -0400 root-tail (0.1.0-3) unstable; urgency=low * Delete stray pixels on the right side (Closes: #130728) -- Marco d'Itri Sat, 26 Jan 2002 14:28:18 +0100 root-tail (0.1.0-2) unstable; urgency=medium * Added conflict with webrt (<= 1.0.6-3). * Added support for named pipes. -- Marco d'Itri Wed, 9 Jan 2002 19:27:22 +0100 root-tail (0.1.0-1) unstable; urgency=medium * New maintainer, new upstream maintainer (Closes: #119989). * The source code is now readable. * Updated to latest policy. * Fixed some small bugs and added some features, updated man page. * Text is redrawed when using SIGUSR2. * Added -reverse and -no-filename options. * Open files are reopened if renamed. * Open files are seeked to start if truncated (Closes: #116580). -- Marco d'Itri Fri, 21 Dec 2001 19:06:28 +0100 root-tail (0.0.10-3) unstable; urgency=low * Fixed build-depends. (Closes: #101228) * Now root-tail works with log rotation, thanks Erwan. (Closes: #101444) -- Peter Novodvorsky Sun, 1 Jul 2001 00:02:30 +0400 root-tail (0.0.10-2) unstable; urgency=low * Fixed default geomerty as Wichert requested. Now 160x20. (Closes: Bug#41414) -- Peter Novodvorsky Fri, 15 Jun 2001 02:26:18 +0400 root-tail (0.0.10-1) unstable; urgency=low * Changed name from rt to root-tail. * New version, new maintainer, cleaned up with more feaures (Closes: #23584, #26191, #28503, #31221, #41411, #41412, #41413, #41415, #91058, #91630, #93878) -- Peter Novodvorsky Sat, 14 Apr 2001 01:10:48 +0400 rt (mbm0606-1.2) frozen unstable; urgency=low * Non-maintainer upload. * debian/control (Maintainer): valid address. * debian/menu: remove "icon=none". * debian/menu: we are "rt", not "Rt". * debian/menu: correct section to "Apps/System" as opposed to "system". * debian/*.ex: dh_make cruft; removed. * debian/dirs: removed unused usr/sbin. -- James Troup Sun, 25 Oct 1998 05:55:14 +0000 rt (mbm0606-1.1) unstable; urgency=low * non-maintainer (binary-only) upload for Alpha * changed YAFHC i386 architecture to 'any' * changed -L/usr/X11/lib to -L/usr/X11R6/lib * current was unused at rt.c:533 because all uses were commented out -- Paul Slootman Mon, 7 Sep 1998 22:29:37 +0200 rt (mbm0606-1) unstable; urgency=low * Fixes 'lack of copyright' bug (bug #many of them) -- Kevin Poorman Sat, 25 Jul 1998 22:53:23 -0500 rt (0.4-1) unstable; urgency=low * Initial Release. -- Kevin Poorman Tue, 19 May 1998 22:53:23 -0500 root-tail-1.2/debian/compat0000644000000000000000000000000207723212705015651 0ustar rootroot000000000000004 root-tail-1.2/debian/rules0000755000000000000000000000123107723212705015530 0ustar rootroot00000000000000#!/usr/bin/make -f #export DH_VERBOSE=1 D=`pwd`/debian/root-tail build: build-stamp build-stamp: dh_testdir $(MAKE) root-tail touch build-stamp clean: dh_testroot dh_testdir $(MAKE) -i clean rm -f build-stamp dh_clean install: build dh_testroot dh_testdir dh_installdirs install root-tail $D/usr/bin/ binary-indep: binary-arch: install dh_testdir dh_testroot dh_installdocs README dh_installchangelogs Changes dh_installman root-tail.man dh_strip dh_link dh_compress dh_fixperms dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-arch binary install root-tail-1.2/debian/copyright0000644000000000000000000000610207723212705016405 0ustar rootroot00000000000000Current package maintainer is Stephen Gran . Current upstream author is Marc Lehmann , http://root-tail.plan9.de/ This package was redebianized by Peter Novodvorsky on Sat, 14 Apr 2001 01:03:40 +0400. Previous package was made by Ender ewigin@petra and maintained by Kevin Poorman. AUTHORS Please tell me if I left anybody out. Mike Baker original author Ivo van der Wijk co-author Marc Lehmann optimized refresh Olexij Tkatchenko correct refresh ;) Daniel Lowe many small improvements andre@aleph.it signal handling Marco D'Itri many fixes Here is a list of all (since I started this list) people that helped by diagnosing a bug: Gunnar Ritter Kevin Bryan Eric Thompson Copyright: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. On Debian GNU/Linux systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. root-tail-1.2/debian/control0000644000000000000000000000073607723212705016064 0ustar rootroot00000000000000Source: root-tail Section: x11 Priority: optional Maintainer: Stephen Gran Build-Depends: debhelper (>=4.0), xlibs-dev Standards-Version: 3.6.1 Package: root-tail Replaces: rt Conflicts: rt (<= mbm0606-1.2), webrt (<= 1.0.6-3) Architecture: any Depends: ${shlibs:Depends} Description: Displays select log files in the X root window Root-tail, is a program that displays one or more log files, on the X root window, through the use of transparent windows. root-tail-1.2/debian/CVS/0000755000000000000000000000000010102521162015067 5ustar rootroot00000000000000root-tail-1.2/debian/CVS/Entries0000644000000000000000000000036010031020163016416 0ustar rootroot00000000000000/changelog/1.1/Wed Aug 27 20:34:13 2003// /compat/1.1/Wed Aug 27 20:34:13 2003// /control/1.1/Wed Aug 27 20:34:13 2003// /copyright/1.1/Wed Aug 27 20:34:13 2003// /dirs/1.1/Wed Aug 27 20:34:13 2003// /rules/1.1/Wed Aug 27 20:34:13 2003// D root-tail-1.2/debian/CVS/Root0000644000000000000000000000003510102521162015733 0ustar rootroot00000000000000:ext:root@rain:/schmorpforge root-tail-1.2/debian/CVS/Repository0000644000000000000000000000002110031020162017155 0ustar rootroot00000000000000root-tail/debian root-tail-1.2/debian/dirs0000644000000000000000000000006307723212705015336 0ustar rootroot00000000000000usr/bin usr/share/man/man1 usr/share/doc/root-tail root-tail-1.2/root-tail.c0000644000000000000000000013454410102521200015274 0ustar rootroot00000000000000/* * Copyright 2001 by Marco d'Itri * Copyright 2000,2001,2002,2003,2004 * Marc Lehmann , * Chris Moore , * and many others, see README * * Original version by Mike Baker. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAS_REGEX #include #endif #define SHADE_X 2 #define SHADE_Y 2 /* some italic fonts still go over the margin - this margin of error cleans up the mess */ #define MARGIN_OF_ERROR 2 /* data structures */ struct logfile_entry { struct logfile_entry *next; char *fname; /* name of file */ char *desc; /* alternative description */ char *buf; /* text read but not yet displayed */ const char *fontname; XFontSet fontset; int font_height; int font_ascent; FILE *fp; /* FILE struct associated with file */ ino_t inode; /* inode of the file opened */ off_t last_size; /* file size at the last check */ unsigned long color; /* color to be used for printing */ const char *colorname; /* color name/string */ int partial; /* true if the last line isn't complete */ int lastpartial; /* true if the previous output wasn't complete */ struct line_node *last; /* last line we output */ int modified; /* true if line is modified & needs displaying */ }; struct line_node { struct line_node *next; struct line_node *prev; struct logfile_entry *logfile; char *line; /* the text of the line (so far) */ int len; /* the length of the line (in bytes) so far */ int wrapped_left; /* true if wrapped from the previous line */ int wrapped_right; /* true if wrapped to the next line */ struct breakinfo *breaks; /* array of indicies to spaces if wrapped_right */ int num_words; /* the number of words in the line */ int free_pixels; /* the number of free pixels to spread out */ }; struct breakinfo { int index; /* index into string of start of substring */ int width; /* width in pixels of start of substring */ int len; /* length of substring */ }; struct displaymatrix { char *line; int len; int offset; int buffer_size; unsigned long color; }; /* global variables */ struct line_node *linelist = NULL, *linelist_tail = NULL; struct displaymatrix *display; int continuation_width = -1; int continuation_color; int continuation_length; /* HACK - ideally listlen will start at however many '~'s will fit on * the screen */ int width = STD_WIDTH, height = STD_HEIGHT, listlen = 50; int win_x = LOC_X, win_y = LOC_Y; int effect_x_space, effect_y_space; /* how much space does shading / outlining take up */ int effect_x_offset, effect_y_offset; /* and how does it offset the usable space */ int do_reopen; struct timeval interval = { 2, 400000 }; /* command line options */ int opt_noinitial, opt_shade, opt_frame, opt_reverse, opt_nofilename, opt_outline, opt_noflicker, opt_whole, opt_update, opt_wordwrap, opt_justify, geom_mask, opt_minspace, reload; const char *command = NULL, *fontname = USE_FONT, *dispname = NULL, *def_color = DEF_COLOR, *continuation = "|| ", *cont_color = DEF_CONT_COLOR; struct logfile_entry *loglist = NULL, *loglist_tail = NULL; Display *disp; Window root; GC WinGC; #if HAS_REGEX struct re_list { regex_t from; const char *to; struct re_list *next; }; struct re_list *re_head, *re_tail; char *transform_to = NULL; regex_t *transformre; #endif /* prototypes */ void list_files (int); void force_reopen (int); void force_refresh (int); void blank_window (int); #ifdef USE_TOON_GET_ROOT_WINDOW Window ToonGetRootWindow(Display *, int, Window *); #endif /* USE_TOON_GET_ROOT_WINDOW */ void InitWindow (void); unsigned long GetColor (const char *); void redraw (int); void refresh (int, int, int, int); void transform_line (char *s); int lineinput (struct logfile_entry *); void reopen (void); void check_open_files (void); FILE *openlog (struct logfile_entry *); static void main_loop (void); void display_version (void); void display_help (char *); void install_signal (int, void (*)(int)); void *xstrdup (const char *); void *xmalloc (size_t); void *xrealloc (void *, size_t); int daemonize (void); /* signal handlers */ void list_files (int dummy) { struct logfile_entry *e; fprintf (stderr, "Files opened:\n"); for (e = loglist; e; e = e->next) fprintf (stderr, "\t%s (%s)\n", e->fname, e->desc); } void force_reopen (int dummy) { do_reopen = 1; } void force_refresh (int dummy) { redraw (1); } void blank_window (int dummy) { XClearArea (disp, root, win_x, win_y, width + MARGIN_OF_ERROR, height, False); XFlush (disp); exit (0); } /* X related functions */ unsigned long GetColor (const char *ColorName) { XColor Color; XWindowAttributes Attributes; XGetWindowAttributes (disp, root, &Attributes); Color.pixel = 0; if (!XParseColor (disp, Attributes.colormap, ColorName, &Color)) fprintf (stderr, "can't parse %s\n", ColorName); else if (!XAllocColor (disp, Attributes.colormap, &Color)) fprintf (stderr, "can't allocate %s\n", ColorName); return Color.pixel; } #ifndef USE_TOON_GET_ROOT_WINDOW static void find_root_window (Display *display, int screen_number) { if (!root) { Atom SWM_VROOT = XInternAtom (display, "__SWM_VROOT", False); Atom NAUTILUS_DESKTOP_WINDOW_ID = XInternAtom (display, "NAUTILUS_DESKTOP_WINDOW_ID", False); root = RootWindow (display, screen_number); Window unused, *windows = 0; unsigned int count; Atom type; int format; unsigned long nitems, bytes_after_return; unsigned char *virtual_root_window; if (XGetWindowProperty (display, root, NAUTILUS_DESKTOP_WINDOW_ID, 0, 1, False, XA_WINDOW, &type, &format, &nitems, &bytes_after_return, &virtual_root_window) == Success && type == XA_WINDOW) { if (XQueryTree (display, *(Window *)virtual_root_window, &unused, &unused, &windows, &count)) root = windows[count - 1]; XFree (virtual_root_window); } else if (XQueryTree (display, root, &unused, &unused, &windows, &count)) { int i; for (i = 0; i < count; i++) { if (XGetWindowProperty (display, windows[i], SWM_VROOT, 0, 1, False, XA_WINDOW, &type, &format, &nitems, &bytes_after_return, &virtual_root_window) == Success && type == XA_WINDOW) { root = *(Window *)virtual_root_window; XFree (virtual_root_window); break; } } XFree (windows); } else fprintf (stderr, "Can't query tree on root window 0x%lx", root); } } #endif /* USE_TOON_GET_ROOT_WINDOW */ void InitWindow (void) { XGCValues gcv; unsigned long gcm; int screen, ScreenWidth, ScreenHeight; struct logfile_entry *e; if (!(disp = XOpenDisplay (dispname))) { fprintf (stderr, "Can't open display %s.\n", dispname); exit (1); } screen = DefaultScreen (disp); ScreenHeight = DisplayHeight (disp, screen); ScreenWidth = DisplayWidth (disp, screen); find_root_window (disp, screen); gcm = GCBackground; gcv.graphics_exposures = True; WinGC = XCreateGC (disp, root, gcm, &gcv); XMapWindow (disp, root); XSetForeground (disp, WinGC, GetColor (DEF_COLOR)); for (e = loglist; e; e = e->next) { char **missing_charset_list; int missing_charset_count; char *def_string; e->fontset = XCreateFontSet (disp, e->fontname, &missing_charset_list, &missing_charset_count, &def_string); if (missing_charset_count) { fprintf (stderr, "Missing charsets in String to FontSet conversion (%s)\n", missing_charset_list[0]); XFreeStringList (missing_charset_list); } if (!e->fontset) { fprintf (stderr, "unable to create fontset for font '%s', exiting.\n", e->fontname); exit (1); } { XFontSetExtents *xfe = XExtentsOfFontSet (e->fontset); e->font_height = xfe->max_logical_extent.height; e->font_ascent = -xfe->max_logical_extent.y; } if (e->font_height > height - effect_y_space) { fprintf(stderr, "\n the display isn't tall enough to display a single line in font '%s'\n", e->fontname); fprintf(stderr, "\n the geometry in use is %d pixels tall\n", height); fprintf(stderr, "\n font '%s' is %d pixels tall\n", e->fontname, e->font_height); if (effect_y_space) fprintf(stderr, "\n the shade or outline options need an extra %d pixel%s of vertical space\n", effect_y_space, effect_y_space == 1 ? "" : "s"); fprintf(stderr, "\n"); exit(1); } } if (geom_mask & XNegative) win_x = win_x + ScreenWidth - width; if (geom_mask & YNegative) win_y = win_y + ScreenHeight - height; { struct logfile_entry *e; for (e = loglist; e; e = e->next) e->color = GetColor (e->colorname); } XSelectInput (disp, root, ExposureMask | FocusChangeMask); } /* * if redraw () is passwd a non-zero argument, it does a complete * redraw, rather than an update. if the argument is zero (and * -noflicker is in effect) then only the lines which have changed * since the last draw are redrawn. * * the rest is handled by regular refresh ()'es */ void redraw (int redraw_all) { XSetClipMask (disp, WinGC, None); refresh (0, 32768, 1, redraw_all); } void draw_text (Display *disp, Window root, GC WinGC, int x, int y, struct line_node *line, int foreground) { if (line->wrapped_right && opt_justify && line->breaks) { int i; for (i = 0; i < line->num_words; i++) XmbDrawString (disp, root, line->logfile->fontset, WinGC, x + line->breaks[i].width + ((i * line->free_pixels) / (line->num_words - 1)) + continuation_width * line->wrapped_left, y, line->line + line->breaks[i].index, line->breaks[i].len); if (line->wrapped_left) { if (foreground) XSetForeground (disp, WinGC, continuation_color); XmbDrawString (disp, root, line->logfile->fontset, WinGC, x, y, continuation, continuation_length); } } else { XmbDrawString (disp, root, line->logfile->fontset, WinGC, x + continuation_width * line->wrapped_left, y, line->line, line->len); if (line->wrapped_left) { if (foreground) XSetForeground (disp, WinGC, continuation_color); XmbDrawString (disp, root, line->logfile->fontset, WinGC, x, y, continuation, continuation_length); } } } /* Just redraw everything without clearing (i.e. after an EXPOSE event) */ void refresh (int miny, int maxy, int clear, int refresh_all) { int lin = 0; int space = height; int offset; unsigned long black_color = GetColor ("black"); struct line_node *line; int step_per_line; int foreground = 0; if (opt_reverse) offset = effect_y_offset; else offset = height + effect_y_offset; miny -= win_y; maxy -= win_y; if (clear && !opt_noflicker) XClearArea (disp, root, win_x, win_y, width + MARGIN_OF_ERROR, height, False); for (line = linelist; line; line = line->next, lin++) { struct displaymatrix *display_line; if (opt_noflicker && lin >= listlen) { int i = listlen; listlen *= 1.5; display = xrealloc(display, listlen * sizeof(struct displaymatrix)); for (; i < listlen; i++) { display[i].line = xstrdup (""); display[i].len = 0; display[i].offset = 0; display[i].buffer_size = 0; } } display_line = display + lin; step_per_line = line->logfile->font_height + effect_y_space; if (step_per_line > space) break; if (!opt_reverse) offset -= step_per_line; offset += line->logfile->font_ascent; miny -= line->logfile->font_height; maxy += line->logfile->font_height; if (offset >= miny && offset <= maxy) { /* if this line is a different than it was, then it * needs displaying */ if (!opt_noflicker || refresh_all || display_line->len != line->len || display_line->color != line->logfile->color || display_line->offset != offset || memcmp (display_line->line, line->line, line->len)) { /* don't bother updating the record of what has been * displayed if -noflicker isn't in effect, since we redraw * the whole display every time anyway */ if (opt_noflicker) { /* update the record of what has been displayed; * first make sure the buffer is big enough */ if (display_line->buffer_size < line->len) { display_line->buffer_size = line->len; display_line->line = xrealloc (display_line->line, display_line->buffer_size); } display_line->len = line->len; display_line->color = line->logfile->color; display_line->offset = offset; memcpy (display_line->line, line->line, line->len); if (clear) { #ifdef DEBUG static int toggle; toggle = 1 - toggle; XSetForeground (disp, WinGC, toggle ? GetColor ("cyan") : GetColor ("yellow")); XFillRectangle (disp, root, WinGC, win_x, win_y + offset - line->logfile->font_ascent, width, step_per_line); #else /* DEBUG */ XClearArea (disp, root, win_x, win_y + offset - line->logfile->font_ascent, width + MARGIN_OF_ERROR, step_per_line, False); #endif /* DEBUG */ } } if (opt_outline) { int x, y; XSetForeground (disp, WinGC, black_color); for (x = -1; x <= 1; x += 2) for (y = -1; y <= 1; y += 2) draw_text (disp, root, WinGC, win_x + effect_x_offset + x, win_y + y + offset, line, foreground = 0); } else if (opt_shade) { XSetForeground (disp, WinGC, black_color); draw_text (disp, root, WinGC, win_x + effect_x_offset + SHADE_X, win_y + offset + SHADE_Y, line, foreground = 0); } XSetForeground (disp, WinGC, line->logfile->color); draw_text (disp, root, WinGC, win_x + effect_x_offset, win_y + offset, line, foreground = 1); } } if (opt_reverse) offset += step_per_line; offset -= line->logfile->font_ascent; miny += line->logfile->font_height; maxy -= line->logfile->font_height; space -= step_per_line; } if (space > 0 && clear) { #ifdef DEBUG XSetForeground (disp, WinGC, GetColor ("orange")); XFillRectangle (disp, root, WinGC, win_x, win_y + offset - (opt_reverse ? 0 : space), width, space); #else /* DEBUG */ XClearArea (disp, root, win_x, win_y + offset - (opt_reverse ? 0 : space), width + MARGIN_OF_ERROR, space, False); #endif } /* at least one of the lines must fit in the allocated area. we've * already checked at initialisation time that all the fonts are small * enough to fit at least one line in the display area, but assert it * again here to be sure */ assert(line != linelist); /* any lines that didn't just get looked at are never going to be, so break the chain */ if (line) line->prev->next = 0; /* and throw them all away */ while (line) { struct line_node *this = line; line = line->next; if (this->logfile && this->logfile->last == this) this->logfile->last = NULL; free (this->line); free (this->breaks); free (this); } if (opt_frame) { XSetForeground (disp, WinGC, GetColor (def_color)); /* note that XDrawRectangle() draws a rectangle one pixel bigger * in both dimensions than you ask for, hence the subtractions. * XFillRectangle() doesn't suffer from this problem */ XDrawRectangle (disp, root, WinGC, win_x - 0, win_y - 0, width - 1, height - 1); } } #if HAS_REGEX void transform_line (char *s) { #ifdef I_AM_Md int i; if (1) { for (i = 16; s[i]; i++) s[i] = s[i + 11]; } s[i + 1] = '\0'; #endif if (transformre) { int i; regmatch_t matched[16]; i = regexec (transformre, s, 16, matched, 0); if (i == 0) { /* matched */ int match_start = matched[0].rm_so; int match_end = matched[0].rm_eo; int old_len = match_end - match_start; int new_len = strlen (transform_to); int old_whole_len = strlen (s); printf ("regexp was matched by '%s' - replace with '%s'\n", s, transform_to); printf ("match is from %d to %d\n", match_start, match_end); if (new_len > old_len) s = xrealloc (s, old_whole_len + new_len - old_len); if (new_len != old_len) { memcpy (s + match_end + new_len - old_len, s + match_end, old_whole_len - match_end); s[old_whole_len + new_len - old_len] = '\0'; } memcpy (s + match_start, transform_to, new_len); printf ("transformed to '%s'\n", s); } else printf ("regexp was not matched by '%s'\n", s); } } #endif /* * appends p2 to the end of p1, if p1 is not null * otherwise allocates a new string and copies p2 to it */ char * concat_line (char *p1, const char *p2) { int l1 = p1 ? strlen (p1) : 0; int l2 = strlen (p2); char *r; assert (p2); if (p1) r = xrealloc(p1, l1 + l2 + 1); else r = xmalloc (l2 + 1); memcpy (r + l1, p2, l2); r[l1 + l2] = 0; return r; } /* * This routine can read a line of any length if it is called enough times. */ int lineinput (struct logfile_entry *logfile) { char buff[1024], *p; int ch; /* HACK-2: add on the length of any partial line which we will be appending to */ int ofs = logfile->buf ? strlen (logfile->buf) : 0; /* this loop ensures that the whole line is read, even if it's * longer than the buffer. we need to do this because when --whole * is in effect we don't know whether to display the line or not * until we've seen how (ie. whether) it ends */ do { p = buff; do { ch = fgetc (logfile->fp); if (ch == '\n' || ch == EOF) break; else if (ch == '\r') continue; /* skip */ else if (ch == '\t') { do { *p++ = ' '; ofs++; } while (ofs & 7); } else { *p++ = ch; ofs++; } } while (p < buff + (sizeof buff) - 8 - 1); if (p == buff && ch == EOF) return 0; *p = 0; p = logfile->buf = concat_line (logfile->buf, buff); } while (ch != '\n' && ch != EOF); logfile->lastpartial = logfile->partial; /* there are 3 ways we could have exited the loop: reading '\n', * reaching EOF, or filling the buffer; the 2nd and 3rd of these * both result in a partial line */ logfile->partial = ch != '\n'; if (logfile->partial && opt_whole) return 0; #if HAS_REGEX transform_line (logfile->buf); #endif return 1; } /* input: reads file->fname * output: fills file->fp, file->inode * returns file->fp * in case of error, file->fp is NULL */ FILE * openlog (struct logfile_entry * file) { struct stat stats; if ((file->fp = fopen (file->fname, "r")) == NULL) { file->fp = NULL; return NULL; } fstat (fileno (file->fp), &stats); if (S_ISFIFO (stats.st_mode)) { if (fcntl (fileno (file->fp), F_SETFL, O_NONBLOCK) < 0) perror ("fcntl"), exit (1); file->inode = 0; } else file->inode = stats.st_ino; if (opt_noinitial) fseek (file->fp, 0, SEEK_END); else /* if (stats.st_size > (listlen + 1) * width) * HACK - 'width' is in pixels - how are we to know how much text will fit? * fseek (file->fp, -((listlen + 2) * width/10), SEEK_END); */ fseek (file->fp, -5000, SEEK_END); file->last_size = stats.st_size; return file->fp; } void reopen (void) { struct logfile_entry *e; for (e = loglist; e; e = e->next) { if (!e->inode) continue; /* skip stdin */ if (e->fp) fclose (e->fp); /* if fp is NULL we will try again later */ openlog (e); } do_reopen = 0; } void check_open_files (void) { struct logfile_entry *e; struct stat stats; for (e = loglist; e; e = e->next) { if (!e->inode) continue; /* skip stdin */ if (stat (e->fname, &stats) < 0) { /* file missing? */ sleep (1); if (e->fp) fclose (e->fp); if (openlog (e) == NULL) continue; if (fstat (fileno (e->fp), &stats) < 0) continue; } if (stats.st_ino != e->inode) { /* file renamed? */ if (e->fp) fclose (e->fp); if (openlog (e) == NULL) continue; if (fstat (fileno (e->fp), &stats) < 0) continue; } if (stats.st_size < e->last_size) { /* file truncated? */ fseek (e->fp, 0, SEEK_SET); e->last_size = stats.st_size; } } } /* * insert a single node in the list of screen lines and return a * pointer to the new node. * the caller MUST then fill in ret->line and ret->len with valid * data. */ static struct line_node * new_line_node (struct logfile_entry *log) { struct line_node *new = xmalloc (sizeof (struct line_node)); new->logfile = log; new->wrapped_left = 0; new->wrapped_right = 0; new->breaks = 0; assert(log); if (!log || !log->last) { new->next = linelist; new->next->prev = new; new->prev = NULL; linelist = new; } else { /* 2 pointers from the new node */ new->next = log->last; new->prev = log->last->prev; /* 2 pointers back to the new node */ if (new->next) new->next->prev = new; if (new->prev) new->prev->next = new; /* if this is a new first entry in the list then update * 'linelist' */ if (log->last == linelist) linelist = new; } /* update the logfile record */ if (log) log->last = new; return new; } /* * this is called after either adding a new line or appending to an * old one. in both cases it's possible that the line no longer fits, * and needs wrapping. this function checks the last line associated * with the supplied logfile. */ static void possibly_split_long_line (struct logfile_entry *log) { char *str = log->last->line; int l = strlen (str); char *p = str; struct line_node *line; int spaces; static struct breakinfo *breaks; static int break_buffer_size; /* only calculate the continuation's width once */ if (continuation_width == -1) { continuation_length = strlen (continuation); continuation_width = XmbTextEscapement (log->fontset, continuation, continuation_length); continuation_color = GetColor (cont_color); /* make an array to store information about the location of * spaces in the line */ if (opt_justify) { break_buffer_size = 32; breaks = xmalloc (break_buffer_size * sizeof (struct breakinfo)); } } do { const char *beg = p; int start_w = log->last->wrapped_left ? continuation_width : 0; int w = start_w; int wrapped = 0; char *break_p = NULL; int width_at_break_p = 0; int prefix_len; spaces = 0; if (opt_justify) breaks[spaces].index = breaks[spaces].width = 0; while (*p) { int cw, len; /* find the length in bytes of the next multibyte character */ len = mblen (p, l); if (len <= 0) len = 1; /* ignore (don't skip) illegal character sequences */ /* find the width in pixels of the next character */ cw = XmbTextEscapement (log->fontset, p, len); if (opt_wordwrap && len == 1 && p[0] == ' ' && p != break_p + 1) { break_p = p; width_at_break_p = w; spaces++; if (opt_justify) { /* increase the size of the 'breaks' array when * necessary */ if (spaces >= break_buffer_size) { break_buffer_size *= 1.5; breaks = xrealloc (breaks, break_buffer_size * sizeof (struct breakinfo)); } /* store information about (a) the location of each * space */ breaks[spaces].index = p + 1 - beg; /* (b) the width (in pixels) of the string up to * this space */ breaks[spaces].width = cw + w - start_w; /* (c) the length of each 'word' */ breaks[spaces-1].len = breaks[spaces].index - breaks[spaces-1].index; } } if (cw + w > width - effect_x_space) { if (p == beg) { fprintf (stderr, "we can't even fit a single character onto the line\n"); if (len == 1) fprintf (stderr, "(the character we couldn't fit was '%c')\n", *p); exit (1); } wrapped = 1; break; } w += cw; p += len; l -= len; } /* if we're wrapping at spaces, and the line is long enough to * wrap, and we've seen a space already, and the space wasn't * the first character on the line, then wrap at the space */ if (!wrapped) break; /* choose where to break the line */ if (opt_wordwrap && break_p && break_p != beg) { prefix_len = break_p - beg; p = break_p; w = width_at_break_p; /* if breaking at a space, skip all adjacent spaces */ while (*p == ' ') { int len = mblen (p, l); if (len != 1) break; p++; } if (opt_justify) { spaces--; breaks[spaces].len--; } } else prefix_len = p - beg; /* make a copy of the tail end of the string */ p = xstrdup (p); /* and reduce the size of the head of the string */ log->last->line = xrealloc (log->last->line, prefix_len + 1); log->last->len = prefix_len; log->last->line[prefix_len] = '\0'; /* note that the head was wrapped on it's right */ log->last->wrapped_right = 1; /* 'spaces' includes any space we broke on; we can only justify * if there's at least one other space */ if (opt_justify && spaces && width - effect_x_space - width_at_break_p < spaces * log->font_height) { int i; log->last->free_pixels = width - effect_x_space - w; log->last->num_words = spaces + 1; log->last->breaks = malloc (log->last->num_words * sizeof (struct breakinfo)); for (i = 0; i < log->last->num_words; i++) log->last->breaks[i] = breaks[i]; } line = new_line_node (log); line->line = p; l = line->len = strlen (p); /* note that the tail end of the string is wrapped at its left */ line->wrapped_left = 1; } while (l); } static void insert_new_line (char *str, struct logfile_entry *log) { struct line_node *new; new = new_line_node (log); new->line = str; new->len = strlen (str); possibly_split_long_line (log); } /* * append something to an existing physical line. this is done * by deleting the file on-screen, concatenating the new data to it * and splitting it again. */ static void append_to_existing_line (char *str, struct logfile_entry *log) { char *old, *new; assert(log); assert(log->last); old = log->last->line; assert(old); new = concat_line (old, str); free (str); log->last->line = new; log->last->len = strlen (new); possibly_split_long_line (log); } static void main_loop (void) { int lin; time_t lastreload; Region region = XCreateRegion (); XEvent xev; struct logfile_entry *lastprinted = NULL; struct logfile_entry *current; int need_update = 1; display = xmalloc (sizeof (struct displaymatrix) * listlen); lastreload = time (NULL); /* Initialize line_node */ for (lin = 0; lin < listlen; lin++) { struct line_node *e = xmalloc (sizeof (struct line_node)); e->line = xstrdup ("~"); e->len = 1; e->logfile = loglist; /* this is only needed to get a color for the '~' */ e->wrapped_left = 0; e->wrapped_right = 0; e->breaks = 0; e->next = NULL; e->prev = linelist_tail; if (!linelist) linelist = e; if (linelist_tail) linelist_tail->next = e; linelist_tail = e; display[lin].line = xstrdup (""); display[lin].len = 0; display[lin].offset = 0; display[lin].buffer_size = 0; } for (;;) { /* read logs */ for (current = loglist; current; current = current->next) { if (!current->fp) continue; /* skip missing files */ clearerr (current->fp); while (lineinput (current)) { need_update = 1; /* if we're trying to update old partial lines in * place, and the last time this file was updated the * output was partial, and that partial line is not * too close to the top of the screen, then update * that partial line */ if (opt_update && current->lastpartial && current->last) { append_to_existing_line (current->buf, current); current->buf = 0; continue; } /* if all we just read was a newline ending a line that we've already displayed, skip it */ if (current->buf[0] == '\0' && current->lastpartial) { free(current->buf); current->buf = 0; continue; } /* print filename if any, and if last line was from * different file */ if (lastprinted != current) { current->last = 0; if (!opt_nofilename && current->desc[0]) { insert_new_line (xstrdup ("["), current); append_to_existing_line (xstrdup (current->desc), current); append_to_existing_line (xstrdup ("]"), current); } } /* if we're dealing with partial lines, and the last * time we showed the line it wasn't finished ... */ if (!opt_whole && current->lastpartial) { /* if this is the same file we showed last then append to the last line shown */ if (lastprinted == current) append_to_existing_line (current->buf, current); else { /* but if a different file has been shown in the * mean time, make a new line, starting with the * continuation string */ insert_new_line (current->buf, current); current->last->wrapped_left = 1; } } else /* otherwise just make a plain and simple new line */ insert_new_line (current->buf, current); current->buf = 0; lastprinted = current; } } if (need_update) { redraw (0); need_update = 0; } else { XFlush (disp); if (!XPending (disp)) { fd_set fdr; struct timeval to = interval; FD_ZERO (&fdr); FD_SET (ConnectionNumber (disp), &fdr); select (ConnectionNumber (disp) + 1, &fdr, 0, 0, &to); } } check_open_files (); if (do_reopen) reopen (); /* we ignore possible errors due to window resizing &c */ while (XPending (disp)) { XNextEvent (disp, &xev); switch (xev.type) { case Expose: { XRectangle r; r.x = xev.xexpose.x; r.y = xev.xexpose.y; r.width = xev.xexpose.width; r.height = xev.xexpose.height; XUnionRectWithRegion (&r, region, region); } break; default: #ifdef DEBUGMODE fprintf (stderr, "PANIC! Unknown event %d\n", xev.type); #endif break; } } /* reload if requested */ if (reload && lastreload + reload < time (NULL)) { if (command && command[0]) system (command); reopen (); lastreload = time (NULL); } if (!XEmptyRegion (region)) { XRectangle r; XSetRegion (disp, WinGC, region); XClipBox (region, &r); refresh (r.y, r.y + r.height, 0, 1); XDestroyRegion (region); region = XCreateRegion (); } } } int main (int argc, char *argv[]) { int i; int opt_daemonize = 0; int opt_partial = 0, file_count = 0; #if HAS_REGEX char *transform = NULL; #endif setlocale (LC_CTYPE, ""); /* try to initialize the locale. */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (arg[0] == '-' && arg[1] != '\0' && arg[1] != ',') { if (arg[1] == '-') arg++; if (!strcmp (arg, "-?") || !strcmp (arg, "-help") || !strcmp (arg, "-h")) display_help (argv[0]); else if (!strcmp (arg, "-V")) display_version (); else if (!strcmp (arg, "-g") || !strcmp (arg, "-geometry")) geom_mask = XParseGeometry (argv[++i], &win_x, &win_y, &width, &height); else if (!strcmp (arg, "-display")) dispname = argv[++i]; else if (!strcmp (arg, "-cont")) continuation = argv[++i]; else if (!strcmp (arg, "-cont-color")) cont_color = argv[++i]; else if (!strcmp (arg, "-font") || !strcmp (arg, "-fn")) fontname = argv[++i]; #if HAS_REGEX else if (!strcmp (arg, "-t")) { transform = argv[++i]; transform_to = argv[++i]; printf ("transform: '%s' to '%s'\n", transform, transform_to); } #endif else if (!strcmp (arg, "-fork") || !strcmp (arg, "-f")) opt_daemonize = 1; else if (!strcmp (arg, "-reload")) { reload = atoi (argv[++i]); command = argv[++i]; } else if (!strcmp (arg, "-shade")) opt_shade = 1; else if (!strcmp (arg, "-outline")) opt_outline = 1; else if (!strcmp (arg, "-minspace")) opt_minspace = 1; else if (!strcmp (arg, "-noflicker")) opt_noflicker = 1; else if (!strcmp (arg, "-frame")) opt_frame = 1; else if (!strcmp (arg, "-no-filename")) opt_nofilename = 1; else if (!strcmp (arg, "-reverse")) opt_reverse = 1; else if (!strcmp (arg, "-whole")) opt_whole = 1; else if (!strcmp (arg, "-partial")) opt_partial = 1; else if (!strcmp (arg, "-update")) opt_update = opt_partial = 1; else if (!strcmp (arg, "-wordwrap")) opt_wordwrap = 1; else if (!strcmp (arg, "-justify")) opt_justify = 1; else if (!strcmp (arg, "-color")) def_color = argv[++i]; else if (!strcmp (arg, "-noinitial")) opt_noinitial = 1; else if (!strcmp (arg, "-id")) { unsigned long id; if (sscanf (argv[++i], "%li", &id) == 1 && id) root = id; } else if (!strcmp (arg, "-interval") || !strcmp (arg, "-i")) { double iv = atof (argv[++i]); interval.tv_sec = (int) iv; interval.tv_usec = (iv - interval.tv_sec) * 1e6; } else { fprintf (stderr, "Unknown option '%s'.\n" "Try --help for more information.\n", arg); exit (1); } } else { /* it must be a filename */ struct logfile_entry *e; const char *fname, *desc, *fcolor = def_color; char *p; file_count++; /* this is not foolproof yet (',' in filenames are not allowed) */ fname = desc = arg; if ((p = strchr (arg, ','))) { *p = '\0'; fcolor = p + 1; if ((p = strchr (fcolor, ','))) { *p = '\0'; desc = p + 1; } } e = xmalloc (sizeof (struct logfile_entry)); e->partial = 0; e->buf = 0; if (arg[0] == '-' && arg[1] == '\0') { if ((e->fp = fdopen (0, "r")) == NULL) perror ("fdopen"), exit (1); if (fcntl (0, F_SETFL, O_NONBLOCK) < 0) perror ("fcntl"), exit (1); e->fname = NULL; e->inode = 0; if (desc == arg) e->desc = xstrdup ("stdin"); else e->desc = xstrdup (desc); } else { e->fname = xstrdup (fname); if (openlog (e) == NULL) perror (fname), exit (1); e->desc = xstrdup (desc); } e->colorname = fcolor; e->partial = 0; e->fontname = fontname; e->last = NULL; e->next = NULL; if (!loglist) loglist = e; if (loglist_tail) loglist_tail->next = e; loglist_tail = e; } } if (!loglist) { fprintf (stderr, "You did not specify any files to tail\n" "use %s --help for help\n", argv[0]); exit (1); } if (opt_update && opt_whole) { fprintf (stderr, "Specify at most one of -update and -whole\n"); exit (1); } else if (opt_partial && opt_whole) { fprintf (stderr, "Specify at most one of -partial and -whole\n"); exit (1); } /* it doesn't make sense to justify if word wrap isn't on */ if (opt_justify) opt_wordwrap = 1; /* HACK-7: do we want to allow both -shade and -outline? */ if (opt_shade && opt_outline) { fprintf (stderr, "Specify at most one of -shade and -outline\n"); exit (1); } if (opt_partial) /* if we specifically requested to see partial lines then don't insist on whole lines */ opt_whole = 0; else if (file_count > 1) /* otherwise, if we're viewing multiple files, default to showing whole lines */ opt_whole = 1; #if HAS_REGEX if (transform) { int i; printf ("compiling regexp '%s'\n", transform); transformre = xmalloc (sizeof (regex_t)); i = regcomp (transformre, transform, REG_EXTENDED); if (i != 0) { char buf[512]; regerror (i, transformre, buf, sizeof (buf)); fprintf (stderr, "Cannot compile regular expression: %s\n", buf); } else printf ("compiled '%s' OK to %x\n", transform, (int)transformre); } #endif if (opt_outline && !opt_minspace) { /* adding outline increases the total width and height by 2 pixels each, and offsets the text one pixel right and one pixel down */ effect_x_space = effect_y_space = 2; effect_x_offset = effect_y_offset = 1; } else if (opt_shade && !opt_minspace) { /* adding a shadow increases the space used */ effect_x_space = abs (SHADE_X); effect_y_space = abs (SHADE_Y); /* if the shadow is to the right and below then we don't need * to move the text to make space for it, but shadows to the left * and above need accomodating */ effect_x_offset = SHADE_X > 0 ? 0 : -SHADE_X; effect_y_offset = SHADE_Y > 0 ? 0 : -SHADE_Y; } else { effect_x_space = effect_y_space = 0; effect_x_offset = effect_y_offset = 0; } InitWindow (); install_signal (SIGINT, blank_window); install_signal (SIGQUIT, blank_window); install_signal (SIGTERM, blank_window); install_signal (SIGHUP, force_reopen); install_signal (SIGUSR1, list_files); install_signal (SIGUSR2, force_refresh); if (opt_daemonize) daemonize (); main_loop (); exit (1); /* to make gcc -Wall stop complaining */ } void install_signal (int sig, void (*handler) (int)) { struct sigaction action; action.sa_handler = handler; sigemptyset (&action.sa_mask); action.sa_flags = SA_RESTART; if (sigaction (sig, &action, NULL) < 0) fprintf (stderr, "sigaction (%d): %s\n", sig, strerror (errno)), exit (1); } void * xstrdup (const char *string) { void *p; while ((p = strdup (string)) == NULL) { fprintf (stderr, "Memory exhausted in xstrdup ().\n"); sleep (10); } return p; } void * xmalloc (size_t size) { void *p; while ((p = malloc (size)) == NULL) { fprintf (stderr, "Memory exhausted in xmalloc ().\n"); sleep (10); } return p; } void * xrealloc (void *ptr, size_t size) { void *p; while ((p = realloc (ptr, size)) == NULL) { fprintf (stderr, "Memory exhausted in xrealloc ().\n"); sleep (10); } return p; } void display_help (char *myname) { printf ("Usage: %s [options] file1[,color[,desc]]" "[options] [file2[,color[,desc]] ...]\n", myname); printf (" -g | -geometry geometry -g WIDTHxHEIGHT+X+Y\n" " -color color use color $color as default\n" " -reload sec command reload after $sec and run command\n" " -id id window id to use instead of the root window\n" " -font FONTSPEC (-fn) font to use\n" " -f | -fork fork into background\n" " -reverse print new lines at the top\n" " -whole wait for \\n before showing a line\n" " -partial show lines even if they don't end with a \\n\n" " -update allow updates to old partial lines\n" " -cont string to prefix continued partial lines with\n" " defaults to \"|| \"\n" " -wordwrap wrap long lines at spaces to avoid breaking words\n" " -shade add shading to font\n" " -outline add black outline to font\n" " -minspace force minimum line spacing\n" " -noinitial don't display the last file lines on\n" " startup\n" " -i | -interval seconds interval between checks (fractional\n" " values o.k.). Default 2.4 seconds\n" " -V display version information and exit\n" "\n"); printf ("Example:\n%s -g 800x250+100+50 -font fixed /var/log/messages,green " "/var/log/secure,red,'ALERT'\n", myname); exit (0); } void display_version (void) { printf ("root-tail version " VERSION "\n"); exit (0); } int daemonize (void) { pid_t pid; switch (pid = fork ()) { case -1: return -1; case 0: break; default: /*printf ("%d\n", pid);*/ exit (0); } if (setsid () == -1) return -1; return 0; }