browser-history/README0100664004704300003100000000331706216306750014102 0ustar colaskoalaBROWSER HISTORY =============== See full docs in the HTML page browser-history.html or at the URL: http://www.inria.fr/koala/colas/browser-history WARNING: browser-history works only on X, not on Windows or Macintosh. Introduction Browser-history came from the will to overcome a Netscape bug: there is no global history, and if you close a window, its whole history is lost. For people browsing lots of sites, having a possibility to track back where one has been before means that you dont have to put everything in your bookmarks file. If you are not sure if a site may be worth remebering, don't add it in your bookmarks. If you need it later, just browse you history files. Later, it came to our minds that this also could be a valuable add-on to people writing experimental browsers, so they dont have to add this functionality to their browser itself. Browser-history is a small and efficient daemon. Real user services could be built on top of the log files it maintains for more possibilities (graphical representation, advanced search options, collective histories). It can be seen as a quick-and-dirty hack wrt to the general solution of using a personal proxy to provide this history and housekeeping facilities. But in the meantime, it is easy to use and it works. Author Colas Nahaboo, Koala project. http://www.inria.fr/koala/colas License The X distribution license: you can do everything with this code (selling it, modifying it), except suing me or using my name in your advertisements, or expecting any kind of support or guarantee. Quick usage guide Just run the program each time you launch your X server. Add the following line into your .xinitrc or equivalent startup file: browser-history & browser-history/Makefile0100664004704300003100000000222207135620530014650 0ustar colaskoala# simplistic makefile # possible flags: -DNO_STRSTR if error with undefined function strstr # targets: #linux: #sunsolaris: solaris #sunos: bsd #decalpha: #SGI: cc browser-history: browser-history.c gcc -ansi -O -Wall -I/usr/X11R6/include browser-history.c \ -o browser-history -L/usr/X11R6/lib -lXmu -lX11 all: gcc -ansi -O -Wall -I/usr/X11R6/include browser-history.c -o browser-history -L/usr/X11R6/lib -lXmu -lX11 cc: cc -I/usr/X11R6/include -ansi -O -o browser-history browser-history.c \ -L/usr/X11R6/lib -lXmu -lX11 bsd: gcc -I/usr/X11R6/include -ansi -O -Wall -DBSD browser-history.c -o browser-history \ -L/usr/X11R6/lib -lXmu -lX11 solaris: gcc -I/usr/X11R6/include -ansi -O -Wall browser-history.c -o browser-history \ -R/usr/X11R6/lib \ -L/usr/X11R6/lib -lXmu -lX11 -ldl -lsocket -lnsl debug : gcc -I/usr/X11R6/include -DDEBUG -ansi -g -Wall browser-history.c -o browser-history \ -L/usr/X11R6/lib -lXmu -lX11 tar: cd ..;tar cfvz browser-history/browser-history.tgz `cat browser-history/FILES_DIST` shar: fshar < FILES | gzip > browser-history.shar.gz clean: rm browser-history browser-history/browser-history.c0100644004704300003100000012172107137333122016542 0ustar colaskoala/*****************************************************************************\ * * * browser-history * * logs all URLs acceded by browser * * * \*****************************************************************************/ /* Copyright Colas Nahaboo 1996, http://www.inria.fr/koala/colas. * See http://www.inria.fr/koala/colas/browser-history */ char *RCS_ID = "$Id: browser-history.c,v 2.8 2000/07/25 15:46:26 colas Exp $"; static char VERSION[8]; /* Compile with: gcc -ansi -Wall browser-history.c -o browser-history -lXmu -lX11 */ #define _POSIX_SOURCE /* for solaris (sigaction) */ #define __USE_FIXED_PROTOTYPES__ /* for sunos (stdio.h) */ #include #include #include #include #include #include #include #include #include #include #ifdef NO_STRSTR char *strstr(); #endif /********************************************************************* usage */ #define E stderr #define P fprintf void usage() { extern char *homedir, *logreldir; P(E, " browser-history v%s\n", VERSION); P(E, " See full docs at the URL: http://www.inria.fr/koala/colas/browser-history\n"); P(E, " USAGE: browser-history [options]\n"); P(E, " Logs in ~/%s/history-log.html all the URLs you went through\n", logreldir); P(E, " You can then browse the log under Netscape or other browsers via the URL:\n"); P(E, " file:%s/%s/history-log.html\n", homedir, logreldir); P(E, " Tracks automagically all already present browser windows, and all new\n"); P(E, " ones created in the future.\n"); P(E, " This version works with Netscape and Arena.\n"); P(E, " URLs can be excluded from logging by putting them, one per line\n"); P(E, " in the file ~/%s/history-log.exclude\n", logreldir); P(E, " then, if an URL begins with a line from this file, it is not logged.\n"); P(E, " In this file, empty lines or lines beginning by # are comments\n"); P(E, " This file is read once at startup, and re-read when receiving the signal 1\n"); P(E, " Options:\n"); P(E, " -display display_name\n"); P(E, " Specifies X display, otherwise contents of $DISPLAY is used\n"); P(E, " -verbose\n"); P(E, " outputs information on what it is doing. useful for debug.\n"); P(E, " -Version\n"); P(E, " prints version number and exit.\n"); P(E, " -logdir directory\n"); P(E, " which directory to store files into? defaults to ~/.browser-history\n"); P(E, " -gzip gzip_filename\n"); P(E, " the complete path to the gzip compressor. Defaults to gzip.\n"); P(E, " E.g: -gzip /usr/gnu/bin/gzip\n"); P(E, " -seconds delay\n"); P(E, " if two entries are made are more than delay seconds apart, an\n"); P(E, " horizontal rule will separate them, else just a simple line break.\n"); P(E, " Defaults to one hour (3600).\n"); P(E, " -replace\n"); P(E, " If there is an already running browser-history on the display, kills\n"); P(E, " it and take its place. Default is to replace it only if the version\n"); P(E, " is older than ours.\n"); P(E, " -noreplace\n"); P(E, " If there is an already running browser-history on the display, aborts.\n"); P(E, " Default is to replace it only if the version is older than ours.\n"); P(E, " -kill\n"); P(E, " If there is an already running browser-history on the display, kills\n"); P(E, " it, then terminates immediately in all cases.\n"); P(E, " -DontGrab\n"); P(E, " Never Grab the X Server, which might cause deadlocks while debugging,\n"); P(E, " when browser-history or gdb tries to print on the\n"); P(E, " grabbed xterm or emacs.\n"); exit(1); } /********************************************************************* types */ typedef struct _BrowserDesc { char *name; int ID; char *xname; char *xclass; } *BrowserDesc; struct _BrowserDesc browsers[] = { #define BT_NONE 0 {"none", BT_NONE, "", ""}, #define BT_NETSCAPE 1 {"netscape", BT_NETSCAPE, "Navigator", "Netscape"}, #define BT_ARENA 2 {"arena", BT_ARENA, "Browser", "Arena"}, #define BT_AMAYA 3 {"amaya", BT_AMAYA, "WWW WYSIWYG_view", "Dialogue"}, #define BT_GENERIC 4 {"generic", BT_GENERIC, ".", "."} }; int browsers_size = 4; typedef struct _Browser_win { Window win; int browser_type; char *url; char *name; int url_changed; } *Browser_win; /******************************************************************* globals */ char *logreldir = ".browser-history"; /* relative to homedir */ char *logfilename = "history-log.html"; char *logfilebase = "history-log"; char *headerfilename = "header.html"; char *logfilepath; /* expansed ~/logfilename */ char *headerfilepath; /* expansed ~/headerfilename */ char *excludefilename = "history-log.exclude"; char *excludefilepath; /* expansed ~/excludefilename */ char *gzippath = "gzip"; char *nilstring = ""; /* returned by get_prop on failure */ char *homedir; /* ~ expansed */ long delay = 3600L; /* if more seconds,
*/ int tag_mode = 0; /* if another browser-history exists: -1 aborts, 1 kills it, 0 kills unless we are an older version 2 kills and exit */ int dontgrab = 0; /* for debug */ char *displayname = 0; /* the display name */ Display *dpy; /* the display */ int verbose = 0; /* verbose mode? */ int must_reinit = 0; /* do we need to re-read init files? */ Browser_win browser_windows; /* the list of existing N wins */ int browser_windows_size = 0; /* its size */ Window tag_window; /* ID of tag window */ int version_num; /* major*1000 + minor */ struct tm last_times; /* time of last entry in log file */ time_t last_time; /* same in seconds */ char **excluded_URLs; /* not logged URLs, array of strings */ int *excluded_URL_lengths; /* array of the sizes of these strings */ int excluded_URLs_size = 0; /* size of the array itself */ Atom WM_NAME; Atom _MOZILLA_URL; Atom ARENA_LOCATION; Atom BROWSER_HISTORY_INFO; Atom BROWSER_HISTORY; Atom LAST_URL; Atom LAST_POS; Atom PROCESS; char *dayname[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; char *monthname[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; /******************************************************************* imports */ extern char *malloc(int bytes); /* WARNING: Dont use these! */ extern char *realloc(void *ptr, int bytes); extern void free(void *ptr); extern Window XmuClientWindow (Display *dpy, Window win); extern char *getenv(const char *); extern char *strdup(const char *); #define streq !strcmp /* extern char *strchr(const char *s, int c); */ extern int atoi(char *s); extern int gethostname(char *name, size_t len); extern int system(char *command); /********************************************************** forward declares */ void initialize(int argc, char **argv); void detect_all_existing_browser_windows(); void mainloop(); void add_new_browser_window(Window win, int browser); void remove_browser_window(Window win); char *get_prop(Window win, Atom name, int offset); void add_URL(int number); void xfree(void *ptr, int offset); char *concat_paths(char *path, char *name1, char *name2); int not_excluded_URL(char *url); void parse_excluded_URLs(); int index_of_win(Window win); int URL_already_present(char *name, char *url); void record_URL(int number, char *name, char *url); void reinit_handler(); int verboseXHandler(Display *dpy, XErrorEvent *error); int terseXHandler(Display *dpy, XErrorEvent *error); int is_browser_window(Window win); int decode_ARENA_LOCATION(char *full_name, char **namep, char **urlp); void init_log_file(int first, FILE *fd); long add_log_entry(char *name, char *url, unsigned int win, char *datestring, struct tm *times, int separator); void reinit(); Window create_tag_window(int mode, int version, char *machine, int pid); char *date2string(struct tm *times); void update_tag_window(Window w, char *name, char *url, unsigned int win, char *datestring, int separator, long filepos); void init_excludefile(); void check_daily_cleanup(); void init_last_entry(); FILE *add_log_day(FILE *fd, struct tm *times); /* Use these to prevent abortion in case of memory full situations */ char *Malloc(int bytes); char *Realloc(void *ptr, int bytes); char *Strdup(char *); #define Free(x) free(x) /* for coherence */ #ifdef BSD extern int sv_sprintfd(char *buffer, char *format, int arg); extern int sv_sprintfs(char *buffer, char *format, char *arg); #else /* !BSD */ #define sv_sprintfs sprintf #define sv_sprintfd sprintf #endif /* !BSD */ /*****************************************************************************\ ************************************ body ************************************* \*****************************************************************************/ int main(int argc, char**argv) { initialize(argc, argv); detect_all_existing_browser_windows(); mainloop(); return 0; } void mainloop() { int n; XEvent event; for (;;) { XNextEvent(dpy, &event); if (must_reinit) /* signal 1 was sent */ reinit(); switch (event.type) { case PropertyNotify: /* prop change on tracked window */ if (-1 == (n = index_of_win(event.xproperty.window))) break; /* window not tracked */ if (browser_windows[n].browser_type == BT_NETSCAPE) { /* Netscape changes first _MOZILLA_URL, then WM_NAME */ if (event.xproperty.atom == WM_NAME) { if (browser_windows[n].url_changed) { /* ok */ add_URL(n); browser_windows[n].url_changed = 0; } /* else ignore, name changed but not due to URL visit */ } else if (event.xproperty.atom == _MOZILLA_URL) { browser_windows[n].url_changed = 1; /* wait for the name */ } } else if (browser_windows[n].browser_type == BT_ARENA) { if (event.xproperty.atom == ARENA_LOCATION) add_URL(n); } else if (browser_windows[n].browser_type == BT_AMAYA) { if (event.xproperty.atom == BROWSER_HISTORY_INFO) add_URL(n); } else if (browser_windows[n].browser_type == BT_GENERIC) { if (event.xproperty.atom == BROWSER_HISTORY_INFO) add_URL(n); } break; case CreateNotify: /* new window created. track it? */ { /* is it a browser window? */ Window client = event.xcreatewindow.window; if ((n = is_browser_window(client))) add_new_browser_window(client, n); } break; case DestroyNotify: /* a tracked window disappear */ if (event.xdestroywindow.window == tag_window) { exit(0); } else { remove_browser_window(event.xdestroywindow.window); } break; } } } /*****************************************************************************\ * initialisations * \*****************************************************************************/ void initialize(int argc, char**argv) { int i; char hostname[256]; /* expands homedir first (for usage) */ if (!(homedir = getenv("HOME"))) homedir = "."; /* version number, extracted from RCS Id string */ { char *p = RCS_ID + 25, *q = p; while (*p && *p != ' ') p++; strncpy(VERSION, q, p - q); version_num = atoi(VERSION) * 1000 + atoi(strchr(VERSION, '.') + 1); } /* parse options, only first letter of option is significant */ for (i = 1; i < argc; i++) { char *arg = argv[i]; if (arg[0] == '-') { switch (arg[1]) { case 'd': /* -display dpyname */ if (++i >= argc) usage (); displayname = argv[i]; continue; case 'v': /* -verbose */ verbose++; continue; case 'V': /* -Version */ fprintf(stderr, "browser-history v%s\n", VERSION); exit(0); continue; case 'l': /* -logdir [.browser-history] */ if (++i >= argc) usage (); logreldir = argv[i]; continue; case 's': /* -seconds delay */ if (++i >= argc) usage (); delay = (long) atoi(argv[i]); if (delay <=0) delay = 3600L; continue; case 'r': /* -replace */ tag_mode = 1; continue; case 'n': /* -noreplace */ tag_mode = -1; continue; case 'k': /* -kill */ tag_mode = 2; continue; case 'g': /* -gzip path */ if (++i >= argc) usage (); gzippath = argv[i]; continue; case 'D': /* -DontGrab */ dontgrab = 1; continue; } } usage(); /* default: unknown option */ } /* expands file names wrt homedir (may have been changed by options) */ logfilepath = concat_paths(homedir, logreldir, logfilename); headerfilepath = concat_paths(homedir, logreldir, headerfilename); excludefilepath = concat_paths(homedir, logreldir, excludefilename); init_excludefile(); /* init arrays */ browser_windows = (Browser_win) Malloc(sizeof(struct _Browser_win) * 1); parse_excluded_URLs(); { /* trap signal 1, make it re-init from files */ struct sigaction action; sigaction(SIGHUP, 0, &action); #ifdef SA_RESTART action.sa_flags |= SA_RESTART; /* else if no SA_RESTART (SUNs) suppose the default behavior is Ok... */ #endif action.sa_handler = reinit_handler; sigaction(SIGHUP, &action, 0); } /* init X */ dpy = XOpenDisplay (displayname); if (!dpy) { fprintf (stderr, "%s: unable to open display \"%s\"\r\n", argv[0], XDisplayName (displayname)); exit (1); } WM_NAME = XInternAtom(dpy, "WM_NAME", False); _MOZILLA_URL = XInternAtom(dpy, "_MOZILLA_URL", False); ARENA_LOCATION = XInternAtom(dpy, "ARENA_LOCATION", False); BROWSER_HISTORY = XInternAtom(dpy, "BROWSER_HISTORY", False); BROWSER_HISTORY_INFO = XInternAtom(dpy, "BROWSER_HISTORY_INFO", False); LAST_URL = XInternAtom(dpy, "LAST_URL", False); LAST_POS = XInternAtom(dpy, "LAST_POS", False); PROCESS = XInternAtom(dpy, "PROCESS", False); /* dont bother users with errors. this is a deamon */ XSetErrorHandler(verbose ? verboseXHandler : terseXHandler); /* create tag window, or abort if previous one runs */ gethostname(hostname, 256); tag_window = create_tag_window(tag_mode, version_num, hostname, getpid()); /* tell X we want to track window creation & destruction */ XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureNotifyMask); init_last_entry(); /* look what was the last entry */ } /*****************************************************************************\ * excluded URLs handling * \*****************************************************************************/ void parse_excluded_URLs() { /* excluded URLs file parsing */ FILE *fd = fopen(excludefilepath, "r"); char lineraw[4000], *line, *end; excluded_URLs = (char **) Malloc(1); excluded_URL_lengths = (int *) Malloc(1); if (fd) { if (verbose) P(E, "reading %s\n", excludefilepath); while (fgets(lineraw, 4000, fd)) { /* trim spaces ate beginning and end */ for (line = lineraw; *line && isspace((int) *line); line++) ; for (end = line + strlen(line); end > line && isspace((int) *(end-1)); end--) ; *end = '\0'; if (line[0] != '\0' && line[0] != '#') { int len = end - line; excluded_URLs = (char **) Realloc(excluded_URLs, (excluded_URLs_size+1) * sizeof(char *)); excluded_URLs[excluded_URLs_size] = Strdup(line); excluded_URL_lengths = (int *) Realloc(excluded_URL_lengths, (excluded_URLs_size+1) * sizeof(int)); excluded_URL_lengths[excluded_URLs_size] = len; excluded_URLs_size++; } } fclose(fd); } } void reinit() { /* clears excluded_URLs, then reloads it */ int i; for (i = 0; i < excluded_URLs_size; i++ ) Free(excluded_URLs[i]); Free(excluded_URLs); Free(excluded_URL_lengths); excluded_URLs_size = 0; parse_excluded_URLs(); must_reinit = 0; } int not_excluded_URL(char *url) { int i; for (i = 0; i < excluded_URLs_size; i++) { if (!strncmp(excluded_URLs[i], url, excluded_URL_lengths[i])) return 0; } return 1; } void init_excludefile() { FILE *fd = fopen(excludefilepath, "a"); if (!fd) { /* cannot open? try to make dir */ char logdir[1024]; sprintf(logdir, "%s/%s", homedir, logreldir); mkdir(logdir, 0777); fd = fopen(excludefilepath, "a"); /* retry now */ } if (fd && ftell(fd) == 0) { /* exclude file is empty, fill it */ if (verbose) P(E, "initialising a new %s\n", excludefilepath); P(fd, "# Put here the start of URLs you want to exclude from the log\n"); P(fd, "# by browser-history\n\n"); P(fd, "# First, we exclude file accesses:\n"); P(fd, "file:\n\n"); P(fd, "# Then, we exclude common search engines...\n"); P(fd, "http://home.netscape.com\n"); P(fd, "http://guide.infoseek.com\n"); P(fd, "http://altavista.digital.com\n"); } if (fd) { /* cannot do anything */ fclose(fd); } } /*****************************************************************************\ * log file formatting * \*****************************************************************************/ void init_log_file(int first, FILE *fd) { int i; FILE *fh = fopen(headerfilepath, "r"); if (!fh) { /* default header */ fprintf(fd, "Global history by browser-history\n"); fprintf(fd, "

Global history by browser-history

\n"); fprintf(fd, "For more info, see the \n"); fprintf(fd, "browser-history home page\n"); fprintf(fd, "

This week

\n"); fprintf(fd, "See at end of file, or jump directly to:\n
    \n"); for(i = 0; i < 7; i++) { fprintf(fd, "
  • %s\n", dayname[i], dayname[i]); } fprintf(fd, "
\n"); fprintf(fd, "

Past weeks

\n"); /* end phrase default headers */ } else { /* user-provided header */ char buffer[255]; int nc, inc; /* copy header in log_file */ while ((nc = fread(buffer, 1, 255, fh)) != 0) { inc = fwrite(buffer, 1, nc, fd); if (inc != nc) { P(E, "Warning: Invalid write (%i != %i) in %s!\n", inc, nc, logfilepath); } } if (!feof(fh)) { P(E, "Warning: Invalid read in %s!\n", headerfilepath); } if (fclose(fh) == EOF) { P(E, "Warning: Can't close %s!\n", headerfilepath); } } if (first) { fprintf(fd, "

None available. This is the first week of usage

\n"); } fprintf(fd, "\n"); /* only present since v2.6 */ fflush(fd); } long /* current filepos in file, or -1 */ add_log_entry(char *name, char *url, unsigned int win, char *datestring, struct tm *times, int separator) { long filepos = -1; FILE *fd = fopen(logfilepath, "a"); if (!fd) { /* cannot open? try to make dir */ char logdir[1024]; sprintf(logdir, "%s/%s", homedir, logreldir); mkdir(logdir, 0777); fd = fopen(logfilepath, "a"); /* retry now */ } if (!fd) { /* cannot do anything */ P(E, "Warning: could not open %s!\n", logfilepath); return filepos; } if (ftell(fd) == 0) { /* init file if empty */ init_log_file(1, fd); last_times = *times; } fd = add_log_day(fd, times); if (fd) { fprintf(fd, "%s %s %s %s 0x%x\n", separator ? "
" : "
", name, url, url, datestring, win); fflush(fd); filepos = ftell(fd); fclose(fd); update_tag_window(tag_window, name, url, win, datestring, separator, filepos); } return filepos; } FILE * /* can be modified... */ add_log_day(FILE *fd, struct tm *times) { if (last_times.tm_year != times->tm_year || last_times.tm_mon != times->tm_mon || last_times.tm_mday != times->tm_mday) { /* not same day as last entry: look if same week */ if (last_times.tm_year != times->tm_year /* not same year */ || (times->tm_yday - last_times.tm_yday) >= 7 /* not same week */ || times->tm_wday < last_times.tm_wday) { /* a sunday has passed */ char command[1000]; char *dir = concat_paths(homedir, logreldir, "."); fclose (fd); if (verbose) P(E, "New week! Starting new log file, archiving previous\n"); sprintf(command, "cd %s; if test -f %s; then mv %s %s-%02d-%02d-%02d.html; %s -9 %s-%02d-%02d-%02d.html; fi", dir, logfilename, logfilename, logfilebase, times->tm_year+1900, times->tm_mon+1, times->tm_mday, gzippath, logfilebase, times->tm_year+1900, times->tm_mon+1, times->tm_mday); system(command); fd = fopen(logfilepath, "a"); if (!fd) return 0; init_log_file(0, fd); fflush(fd); /* we close the file for not mixing data with the following shell * command on some OSes and NFS, we reopen afterwards * fix courtesy of canon@ecoledoc.ibp.fr (Hubert CANON) */ fclose(fd); sprintf(command, "cd %s; for i in `ls -1r %s-[0-9]*.html*`; do echo \"`basename $i .html.gz`
\">>%s; done; echo '
' >> %s", dir, logfilebase, logfilename, logfilename); system(command); Free(dir); fd = fopen(logfilepath, "a"); if (!fd) return 0; } if (verbose) P(E, "New day! adding day chapter in log file\n"); /* warning: previous to v2.6, there was no in day names */ fprintf(fd, "

%s %d %s %d

\n", dayname[times->tm_wday], dayname[times->tm_wday], times->tm_mday, monthname[times->tm_mon], times->tm_year+1900); } /* same day, do nothing */ return fd; } char * date2string(struct tm *times) { static char datestring[40]; sprintf(datestring, "%d/%02d/%02d-%02d:%02d:%02d", times->tm_year+1900, times->tm_mon + 1, times->tm_mday, times->tm_hour, times->tm_min, times->tm_sec); return datestring; } /*****************************************************************************\ * callbacks & handlers * \*****************************************************************************/ void reinit_handler(int sig, void *sip, void *uap) { must_reinit = 1; } int terseXHandler(Display *dpy, XErrorEvent *error) {return 0;} int verboseXHandler(Display *dpy, XErrorEvent *error) { char buffer[256]; XGetErrorText(dpy, error->request_code, buffer, 255); P(E, "(verbose mode) browser-history X Error: %s\n", buffer); return 0; } /*****************************************************************************\ * window handling * \*****************************************************************************/ void detect_all_existing_browser_windows() { Window dummy, *children = NULL, client; unsigned int i, nchildren = 0; if (!XQueryTree (dpy, DefaultRootWindow(dpy), &dummy, &dummy, &children, &nchildren)) { return; } for (i = 0; i < nchildren; i++) { int browser; client = XmuClientWindow (dpy, children[i]); if (client != None && (browser = is_browser_window(client))) { add_new_browser_window(client, browser); } } if (children) XFree(children); } int is_browser_window(Window win) { XClassHint class_hints; char *prop; int result = 0; if (XGetClassHint(dpy, win, &class_hints)) { int b; for (b = 1; b < browsers_size; b++) { /* class must match, name can be a substring */ if (streq(class_hints.res_class, browsers[b].xclass) && strstr(class_hints.res_name, browsers[b].xname)) { result = b; goto found; } } found: XFree(class_hints.res_name); XFree(class_hints.res_class); } if (!result && (nilstring != (prop = get_prop(win, BROWSER_HISTORY_INFO, 0)))) { result = BT_GENERIC; xfree(prop, 0); } return result; } /*****************************************************************************\ * browser window list management * \*****************************************************************************/ void add_new_browser_window(Window win, int browser) { int i; /* check it is not already registered */ for (i = 0; i < browser_windows_size; i++) if (browser_windows[i].win == win) return; /* append and init new window to list of windows monitered */ browser_windows = (Browser_win) Realloc(browser_windows, sizeof(struct _Browser_win) * (browser_windows_size+1)); browser_windows[browser_windows_size].win = win; browser_windows[browser_windows_size].browser_type = browser; browser_windows[browser_windows_size].url_changed = 0; browser_windows[browser_windows_size].url = Strdup(""); browser_windows[browser_windows_size].name = Strdup(""); browser_windows_size++; if (verbose) P(E, "monitoring %s window: 0x%x [%d]\n", browsers[browser].name, (unsigned int) win, browser_windows_size - 1); /* track URL changes and window destruction */ XSelectInput(dpy, win, PropertyChangeMask|StructureNotifyMask); /* add the current URL to the log */ add_URL(browser_windows_size - 1); } void remove_browser_window(Window win) { int i, n; /* check it is already registered */ for (i = 0; i < browser_windows_size; i++) if (browser_windows[i].win == win) { /* deletes from list, compacts */ n = i; Free(browser_windows[n].url); Free(browser_windows[n].name); browser_windows_size--; for (; i< browser_windows_size; i++) { browser_windows[i] = browser_windows[i+1]; } if (verbose) P(E, "%s window closed: 0x%x [%d]\n", browsers[browser_windows[n].browser_type].name, (unsigned int) win, n); return; } } int index_of_win(Window win) { int i; for (i = 0; i < browser_windows_size; i++) if (browser_windows[i].win == win) return i; return -1; /* -1 if not found */ } /*****************************************************************************\ * current URLs remembered * \*****************************************************************************/ int URL_already_present(char *name, char *url) { int i; for (i = 0; i < browser_windows_size; i++) if (streq(name, browser_windows[i].name) && streq(url, browser_windows[i].url)) return 1; return 0; } void record_URL(int n, char *name, char *url) { Free(browser_windows[n].url); Free(browser_windows[n].name); browser_windows[n].url = Strdup(url); browser_windows[n].name = Strdup(name); } /*****************************************************************************\ * log entries * \*****************************************************************************/ void add_URL(int number) { time_t seconds; struct tm *times; char *name, *url; char *full_name = 0, *ns_name = 0; Window win; if (number < 0) return; win = browser_windows[number].win; if (browser_windows[number].browser_type == BT_NETSCAPE) { /* netscape */ ns_name = get_prop(win, WM_NAME, 0); /* name */ if (!strncmp("Netscape:", ns_name, 9)) { /* skip leading Netscape: */ if (ns_name[9] == ' ') { name = ns_name + 10; /* skip also blank if there */ } else { name = ns_name + 9; } } else { name = ns_name; } url = get_prop(win, _MOZILLA_URL, 0); } else if (browser_windows[number].browser_type == BT_ARENA) { /* arena */ if (nilstring == (full_name = get_prop(win, ARENA_LOCATION, 0))) return; if (!decode_ARENA_LOCATION(full_name, &name, &url)) { xfree(full_name, 0); return; } } else if (browser_windows[number].browser_type == BT_AMAYA) { /* amaya */ if (nilstring == (full_name = get_prop(win, BROWSER_HISTORY_INFO, 0))) return; if (!decode_ARENA_LOCATION(full_name, &name, &url)) { xfree(full_name, 0); return; } } else if (browser_windows[number].browser_type == BT_GENERIC) { /* generic */ if (nilstring == (full_name = get_prop(win, BROWSER_HISTORY_INFO, 0))) return; if (!decode_ARENA_LOCATION(full_name, &name, &url)) { xfree(full_name, 0); return; } } else return; /* unknown browser */ if (url != nilstring && not_excluded_URL(url) && !URL_already_present(name, url)) { char *datestring; time(&seconds); times = localtime(&seconds); datestring = date2string(times); add_log_entry(name, url, (unsigned int) win, datestring, times, (((long) seconds - (long)last_time) > delay)); last_times = *times; if (verbose) { P(E, "Adding URL: %s\n", url); P(E, " named: %s\n", name); P(E, " date: %s - Window: 0x%x [%d]\n", datestring, (unsigned int) win, number); P(E, " (debug): seconds: %ld, since_last: %ld\n", (long) seconds, ((long) seconds - (long)last_time)); } record_URL(number, name, url); last_time = seconds; } if (browser_windows[number].browser_type == BT_NETSCAPE) xfree(ns_name, 0), xfree(url, 0); else /* arena, amaya, generic */ xfree(full_name, 0); } /*****************************************************************************\ * Tag Window * \*****************************************************************************/ /* For identification purposes, browser-window maintains an unmapped * "Tag Window" window whose ID is stored on the root window of each * managed screen in the BROWSER_HISTORY property * This window has the following properties on it: * - BROWSER_HISTORY holding the window ID, to prove process is still alive * - LAST_URL a text property, concatenation of null terminated strings * prefixed by a tag=. e.g: * URL=url_text\0TITLE=page_title\0DATE=date_string\0BROWSER_WINDOW_ID=id\0 * This property will be updated on each new entry in the log file. * at start, it exists but is empty (length = 0) * - LAST_POS (since v2.7) a text property containing the index (ascii number) * of the last updated position in the current log file. This allows * external processes to read the log file and stop at the logical end * in case they read just while we are appending. Can be -1 if not defined * - PROCESS a text property, concatenation of null terminated strings * prefixed by a tag=, giving a description of the running process: * version a number (card32) made by 1000*major+minor: v3.4 => 3004 * date date of launch of the process (string) * machine the name of the machine on which the process runs * pid its process id * This property is updated once at process start * e.g: VERSION=1009\0DATE=96/05/16-09:17:08\0MACHINE=koala\0PID=4345\0 * * Destroying this window will terminate properly the currently running * browser-history process */ Window create_tag_window(int mode, int version, char *machine, int pid) { XSetWindowAttributes wa; Window w; char string[1024]; int len = 0; Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after_return; unsigned char *property; int result; char *datestring; time_t seconds; struct tm *times; time(&seconds); times = localtime(&seconds); datestring = date2string(times); if (!dontgrab) XGrabServer(dpy); /* to avoid race conditions */ result = XGetWindowProperty(dpy, DefaultRootWindow(dpy), BROWSER_HISTORY, 0, 1, 0, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytes_after_return, &property); if (result != Success || actual_type != XA_WINDOW) { if (result == Success) XFree(property); goto OK; /* no previous property */ } w = ((long *) property)[0]; XFree(property); result = XGetWindowProperty(dpy, w, BROWSER_HISTORY, 0, 1, 0, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytes_after_return, &property); if (result != Success || actual_type != XA_WINDOW || w != ((long *) property)[0]) { if (result == Success) XFree(property); goto OK; /* not a browser-history window */ } XFree(property); if (mode == -1) { XUngrabServer(dpy); if (verbose) P(E, "a previous browser-history is running, aborting\n"); exit(0); } if (mode == 1 || mode == 2) { if (verbose) P(E, "killing previous browser-history\n"); XDestroyWindow(dpy, w); } if (mode == 2) exit(0); if (mode == 0) { /* check version */ int old_version; result = XGetWindowProperty(dpy, w, PROCESS, 0, 63, 0, XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after_return, &property); old_version = atoi(strchr(property, '=') + 1); XFree(property); if (version > old_version) { if (verbose) P(E, "killing previous version (%d.%d) of browser-history\n", old_version/1000, old_version%1000); XKillClient(dpy, w); } else { XUngrabServer(dpy); if (verbose) P(E, "same or more recent version (%d.%d) of browser-history running, aborting\n", old_version/1000, old_version%1000); exit(0); } } OK: wa.override_redirect = True; w = XCreateWindow (dpy, DefaultRootWindow(dpy), -100, -100, 10, 10, 0, 0, InputOnly, CopyFromParent, CWOverrideRedirect, &wa); /* set pointer to the tag window on root */ XChangeProperty(dpy, DefaultRootWindow(dpy), BROWSER_HISTORY, XA_WINDOW, 32, PropModeReplace, (unsigned char *) &w, 1); /* set properties on the tag window */ XChangeProperty(dpy, w, BROWSER_HISTORY, XA_WINDOW, 32, PropModeReplace, (unsigned char *) &w, 1); XChangeProperty(dpy, w, LAST_URL, XA_STRING, 8, PropModeReplace, (unsigned char *) "", 0); XChangeProperty(dpy, w, LAST_POS, XA_STRING, 8, PropModeReplace, (unsigned char *) "", 0); len += sv_sprintfd(string, "VERSION=%d", version) + 1; /* must be 1st */ len += sv_sprintfs(string + len, "DATE=%s", datestring) + 1; len += sv_sprintfs(string + len, "MACHINE=%s", machine) + 1; len += sv_sprintfd(string + len, "PID=%d", pid) + 1; XChangeProperty(dpy, w, PROCESS, XA_STRING, 8, PropModeReplace, (unsigned char *) string, len); XUngrabServer(dpy); return w; } void update_tag_window(Window w, char *name, char *url, unsigned int win, char *datestring, int separator, long filepos) { char string[8000]; int len = 0; len += sv_sprintfs(string, "URL=%s", url) + 1; /* must be 1st */ len += sv_sprintfs(string + len, "TITLE=%s", name) + 1; len += sv_sprintfs(string + len, "DATE=%s", datestring) + 1; len += sv_sprintfd(string + len, "BROWSER_WINDOW_ID=%d", win) + 1; XChangeProperty(dpy, w, LAST_URL, XA_STRING, 8, PropModeReplace, (unsigned char *) string, len); len = 0; len += sv_sprintfs(string, "%ld", filepos) + 1; XChangeProperty(dpy, w, LAST_POS, XA_STRING, 8, PropModeReplace, (unsigned char *) string, len); } /*****************************************************************************\ * browser-specific stuff * \*****************************************************************************/ int /* 0=error */ decode_ARENA_LOCATION(char *full_name, char **namep, char **urlp) { char *p = full_name, *end; *urlp = *namep = 0; while ((end = strchr(p, '='))) { if (!strncmp("URL", p, 3)) { *urlp = end + 1; } else if (!strncmp("TITLE", p, 5)) { *namep = end + 1; } p = end + strlen(end) + 1; } return (*urlp && *namep) ? 1 : 0; } /*****************************************************************************\ * safe-mallocs * \*****************************************************************************/ /* package used to run even in low-memory situations: * Malloc, Realloc, Strdup waits 10s and retry in case of no more available * memory */ #define GUARD_FUNCTION(original, guarded, ret_type, arg_types, args) \ ret_type guarded arg_types {void *retvalue; \ while(!(retvalue = original args)) sleep(10); \ return (ret_type) retvalue;} GUARD_FUNCTION(malloc, Malloc, char *, (int bytes), (bytes)) GUARD_FUNCTION(realloc, Realloc, char *, (void *ptr, int bytes), (ptr, bytes)) GUARD_FUNCTION(strdup, Strdup, char *, (char *string), (string)) /*****************************************************************************\ * string utils * \*****************************************************************************/ /* creates a memory leak. to be called only on inits */ char * concat_paths(char *path, char *name1, char *name2) { char *new = (char *) Malloc(strlen(path) + strlen(name1) + strlen(name2) + 3); strcpy(new, path); strcat(new, "/"); strcat(new, name1); strcat(new, "/"); strcat(new, name2); return new; } #ifdef BSD /* sysv sprintf emulation */ int sv_sprintfd(char *buffer, char *format, int arg) { sprintf(buffer, format, arg); return strlen(buffer); } int sv_sprintfs(char *buffer, char *format, char *arg) { sprintf(buffer, format, arg); return strlen(buffer); } #endif /* BSD */ /*****************************************************************************\ * X utils * \*****************************************************************************/ char * get_prop(Window win, Atom name, int offset) { Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after; unsigned char *buffer = 0; if (Success == XGetWindowProperty(dpy, win, name, 0, 8000, 0, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &buffer) && buffer && nitems) { buffer[nitems] = '\0'; if (!(actual_type == XA_STRING && (strlen((char *)buffer) >= offset))) { XFree(buffer); buffer = 0; } /* else ok */ } else { if (buffer) XFree(buffer); buffer = 0; } return buffer ? (char *) buffer + offset : nilstring; } void xfree(void *ptr, int offset) { if (ptr && (ptr != nilstring)) XFree(((char *) ptr) - offset); } /*****************************************************************************\ * date & time utils * \*****************************************************************************/ /* on startup, look in last 60 chars when was last entry added */ void init_last_entry() { FILE *fd; /* on failure to find date, set time to 1970/01/01 */ last_times.tm_year = 70; last_times.tm_mon = 0; last_times.tm_mday = 1; last_times.tm_hour = last_times.tm_min = last_times.tm_sec = 0; last_time = 0; if ((fd = fopen(logfilepath, "r"))) { char buffer[61], *line = buffer; int y,m,d,h,mm,s; fseek(fd, 0L, SEEK_END); /* goto end, fetch start of line */ fseek(fd, -60L, SEEK_CUR); /* move back at start of buffer */ fread(buffer, 60, 1, fd); /* read them into buffer */ buffer[60] = '\0'; line = strstr(buffer, ""); /* look for occurence of */ if (line && 6 == sscanf(line, " %d/%02d/%02d-%02d:%02d:%02d", &y,&m,&d,&h,&mm,&s)) { if (y < 100) /* Fix Y2K bug */ last_times.tm_year = y; /* Assume before 2000 */ else last_times.tm_year = y - 1900; /* y contains full year */ last_times.tm_mon = m - 1; last_times.tm_mday = d; last_times.tm_hour = h; last_times.tm_min = mm; last_times.tm_sec = s; last_time = mktime(&last_times); /* compute day of week */ last_times = *localtime(&last_time); } fclose(fd); } if (!last_time) last_time = mktime(&last_times); if (verbose) P(E, "Last entry was at: %d/%02d/%02d-%02d:%02d:%02d (day of week: %d (%s), day of year: %d)\n", last_times.tm_year+1900, last_times.tm_mon + 1, last_times.tm_mday, last_times.tm_hour, last_times.tm_min, last_times.tm_sec, last_times.tm_wday, dayname[last_times.tm_wday], last_times.tm_yday); } /*****************************************************************************\ * portability * \*****************************************************************************/ #ifdef NO_STRSTR /* *---------------------------------------------------------------------- * * strstr -- * * Locate the first instance of a substring in a string. * * Results: * If string contains substring, the return value is the * location of the first matching instance of substring * in string. If string doesn't contain substring, the * return value is 0. Matching is done on an exact * character-for-character basis with no wildcards or special * characters. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * strstr(string, substring) register char *string; /* String to search. */ char *substring; /* Substring to try to find in string. */ { register char *a, *b; /* First scan quickly through the two strings looking for a * single-character match. When it's found, then compare the * rest of the substring. */ b = substring; if (*b == 0) { return string; } for ( ; *string != 0; string += 1) { if (*string != *b) { continue; } a = string; while (1) { if (*b == 0) { return string; } if (*a++ != *b++) { break; } } b = substring; } return (char *) 0; } #endif /* NO_STRSTR */ browser-history/browser-history.html0100664004704300003100000004303407137333061017270 0ustar colaskoalabrowser-history web page

The browser-history web page

This page describes the utility browser-history, a client-side X daemon maintaining a browser-independent global history of all the web sites you visited.

On this page ( http://www.inria.fr/koala/colas/browser-history) you will find:



Introduction

Browser-history came from the will to overcome a Netscape bug: there was no global history, and if you close a window, its whole history is lost. For people browsing lots of sites, having a possibility to track back where one has been before means that you dont have to put everything in your bookmarks file. If you are not sure if a site may be worth remebering, don't add it in your bookmarks. If you need it later, just browse your history files.

Later, it came to our minds that this also could be a valuable add-on to people writing experimental browsers, so they dont have to add this functionality to their browser itself, and to users able thus to track their web history regardless of the browser they used.

Browser-history is a small and efficient daemon. Real user services could be built on top of the log files it maintains for more possibilities (graphical representation, advanced search options, collective histories). It can be seen as a quick-and-dirty hack wrt to the general solution of using a personal proxy to provide this history and housekeeping facilities. But in the meantime, it is easy to use and it works.

And now that Netscape has a semi-decent history, browser-history is still valuable, as it is difficult to search in the netscape history, its file format is not defined, and entries expire after some time.

Author

Colas Nahaboo, Koala project.

License: Open Source

The X distribution license: you can do everything with this code (selling it, modifying it), except suing me or using my name in your advertisements, or expecting any kind of support or guarantee.


Quick usage guide

Just run the program each time you launch your X server. Just add the following line into your .xinitrc or equivalent startup file:

browser-history &


Installation guide

Compilation is straigthforward. See Makefile, but normally, just typing make is sufficient. It only uses Xlib and Xmu, not Xt. You must just install the resulting binary "browser-history" somewhere in your PATH. I tested it on linux, DEC alpha OSF/1, SGI, sun solaris 5.5, sun sunos 4.1.3.


Sample result

You can then open with your browser the URL:
file:$HOME/.browser-history/history-log.html
(replace $HOME by your homedir), and you will see:
     
     
     
     
     
     
     
     
     
     
     
     

Tuesday 28 May 1996


Colas's home page http://www.inria.fr/koala/colas/ 1996/05/28-08:38:34 0x6004a64
Jean-Michel's home page http://www.inria.fr/koala/jml/jml.html 1996/05/28-08:42:07 0x6004a64
phk's home page http://www.inria.fr/koala/phk.html 1996/05/28-10:22:32 0x6004a64

You can click on the listed URLs to go back to them, each entry is listed with the page name in bold, followed by the clickable URL, then the date and time you loaded it, and the X ID of the browser window.

An horizontal rule indicates that at least one hour separates two entries. A file holds one week of history, past weeks are accessibles via links at the top of the page.


Reference Manual

browser-history logs in ~/.browser-history/history-log.html all the URLs you went through. You can then browse the log under Netscape or other browsers via the URL: file:~/.browser-history/history-log.html (replace the ~ by your home directory)

browser-history tracks automagically all already present browser windows, and all new ones created in the future. This program has no user interface. It just appens information to a log file in html format so you can browse it through a web browser. If more that one hour has passed since last entry, it draws an horizontal lines, and adds H1 headers to delimit new days. Each week (sunday mornings), it archives the week history, compresses it by gzip (that you must have in your path), and starts a new history with links to the older ones. To make room you can just remove the obsolete history files.

This version works with Netscape, Arena and Amaya.

URLs can be excluded from logging by putting them, one per line in the file ~/.browser-history/history-log.exclude. Then, if an URL begins with a line from this file, it is not logged. In this file, empty lines or lines beginning by # are comments This file is read once at startup, and re-read when receiving the signal 1. e.g:

    # We exclude local files
    file:
    # Exclude search engines...
    http://home.netscape.com
    http://guide.infoseek.com

browser-history maintains a hidden window on the X display, whose ID is given by the property BROWSER_HISTORY on the root window. If this window is killed, it quits gracefully. On this sub-window the following properties are maintained:

  • The same BROWSER_HISTORY property to check that this is actually a browser-history window
  • PROCESS, string describing where this process is running, made of null-terminated strings of the form:
    • VERSION=version_number (= 1000 * major + minor)
    • DATE, e.g: DATE=1996/05/28-10:08:26
    • MACHINE=host name
  • LAST_URL, string describing the last entry added to the log, made of null-terminated strings of the form:
    • URL=last_url_logged
    • TITLE=its_document_name
    • DATE=the_date_it_was_logged_at
    • BROWSER_WINDOW_ID=the_X_ID_of_the_browser_window
  • LAST_POS (since v2.7) a text property containing the index (ascii number) * of the last updated position in the current log file. This allows * external processes to read the log file and stop at the logical end * in case they read just while we are appending. Can be -1 if not defined

You can customise the header of the newly created weekly log files by putting the header you want to use as an header.html file in the ~/browser-history directory. The default header is the first part of a newly generated file upto the line:

<!-- EndOfHeader -->

When browser-history is run, it looks if another one is running, and by default it kills the previous one if it is an older version. Otherwise, it the new one is the same version number or older, it just aborts.

Options

All options can be given by their first letter: you can specify either -verbose or -v, but you cannot group options, e.g. you must say -v -k, but not -vk

-display display_name
Specifies X display, otherwise contents of $DISPLAY is used
-verbose
outputs information on what it is doing. useful for debug.
-Version
prints version number and exit.
-logdir directory
which directory to store files into? defaults to ~/.browser-history
-gzip gzip_filename
the complete path to the gzip compressor. Defaults to gzip.
E.g: -gzip /usr/gnu/bin/gzip
-seconds delay
if two entries are made are more than delay seconds apart, an horizontal rule will separate them, else just a simple line break. Defaults to one hour (3600).
-replace
If there is an already running browser-history on the display, kills it and take its place. Default is to replace it only if the version is older than ours.
-noreplace
If there is an already running browser-history on the display, aborts. Default is to replace it only if the version is older than ours.
-kill
If there is an already running browser-history on the display, kills it, then terminates immediately in all cases.
-DontGrab
Never Grab the X Server, which might cause deadlocks while debugging, when browser-history or gdb tries to print on the grabbed xterm or emacs.

Specifications of the log files

A log file can have some decorative HTML to represent days, but each entry has the form: (the leading br can also be an hr)

<b>name</b> <a href="URL">URL</a> yyyy/mm/dd-hh:mn:ss <small>windowid</small>
where the following items are:
  • name the name of the document (window title)
  • URL its URL
  • yyyy/mm/dd-hh:mn:ss year, month number, day number, hour, minutes, seconds. Everything as 2-digit numbers, except year (since v2.5)
  • windowid the X window ID of the browser window, in hexadecimal
Note: Before version 2.4, the 4 sub-parts were separated by newlines, but since 2.4, they are only blank-separated to ease searching for URL in log files via "grep".
Note:Before version 2.5, the year was stored in 2 digits. Now it is stored in 4 (or more :-) digits, to fix this Y2K bug
Note:Since version 2.6, you should skip the header by searching for the string <!-- EndOfHeader --> and parse entries till the end, the last one being possibly incomplete in case of concurrent update (you can check the LAST_POS property if you want to avoid this problem)
The logic is thus to parse log files made after mid-97 (v 2.4) is to open the log, and:
  1. Skip beginning up to string <!-- EndOfHeader -->
    If not found, skip to first <hr><p><h1>
  2. now position to next entry beginning with r> <b> at position A. If we cannot find one, we are done.
  3. verify that the entry is valid by checking that a string </small> exists at position B before the end of file
  4. process the entry that is being offsets A and B
  5. repeat last 2 steps


How to download it

By ftp on ftp://koala.inria.fr/pub/browser-history

Supported browsers

Currently supported browsers are:

How to add support in the browser

You can add support for browser-history in any browser. See for instance the description of the Arena client communication system.

Just add on each browser window a string X property named BROWSER_HISTORY_INFO (and type XA_STRING) of the form (C printf string below):

"URL=%s\0TITLE=%s\0\0"
where the first %s is the current URL, and the second the document name.

IMPORTANT! This property MUST be present and MUST contain some value BEFORE mapping (managing) the window, for browser-history to detect it. Otherwise, your browser windows will be ignored. If this proves too difficult for you, you can make the detection of your browser windows built-in in browser-history, search the string BT_AMAYA and add cases for your browser, and send me the patches.

Example from arena: each time a new URL is visited, it triggers this function on the window displaying the document at the same time as it updates the X window title by a call to XStoreName(dpy, win, title).

void SetXProperties(	    /* sets info for browser-history on each new URL */
    Display *dpy,	    /* the X display */
    Window win,		    /* the main document display window */
    char *url,		    /* the URL being visited */
    char *title)	    /* the <TITLE> of the document */
{
    static Atom property_name = 0;
	                                    /* 13 is strlen("URL=0TITLE=00") */
    int v_size = strlen(title) + strlen(url) + 13;
    char *v = (char *) malloc(v_size);	/* or alloca, but dont call free */
    sprintf(v, "URL=%s%cTITLE=%s%c", url, 0, title, 0);
    if (!property_name)
	property_name = XInternAtom(dpy, "BROWSER_HISTORY_INFO", False);
    XChangeProperty(dpy, win, property_name, XA_STRING, 8, PropModeReplace,
			v, v_size);
    free(v);
}


News

History of changes, both to the browser-history program and to this page:
v2.8 - 24-Jul-2000
  • bugfix: since 2.6, internal links of default generated headers were false
v2.7 - 20-Jul-2000
  • new X property LAST_POS on the browser-history window to give last valid position in log file
  • summary of use less terse
v2.6 - 08-Jun-2000
  • If a file "header.html" exists in ~/.browser-history, use it as a header to start new history-log files with. Patch contributed by Philippe Le Hégaret
  • Day names have <a> marquers on them to allow jumping directly to them from the header. Patch contributed by Philippe Le Hégaret
v2.5 - 06-Aug-1999
  • In log, links to the previous weeks are now relative (were absolute). Patch contributed by Bert Bos
  • All years in dates (in logs and in previous week filenames) are now on 4 digits. This was a Y2K bug! :-) Patch contributed by Bert Bos
  • Some users edited the netscape binary to remove the leading Netscape: in window names... I had to adapt to it.
v2.4 - 24-Aug-97
Log entries are now on the same line, to ease grep searches in files.
v2.3 - 14-Feb-97
bugfix: on creating a new file on sundays, one some OSes (SunOs4+NFS), the list of previous histories was empty. Problem detected and fixed by Hubert CANON
v2.2 - 03-Oct-96
built-in support of the last Amaya version (new official editor+browser of the web consortium)
v2.1 - 13-Sep-96
you can add support to your browser without recompiling or restarting browser-history, see Supported browsers
new option -Version, prints version and exits.
v1.18 - 25-Jun-96
Past weeks stored most recent first instead of oldest first at top of history file.
v1.17 - 30-May-96
Works on SunOS and on pure BSD system, where you must add the compilation flag -DBSD or type "make bsd".
new option -DontGrab for debug.
v1.16 - 28-May-96
Works even if gzip is not installed, and gzip location settable by the new -gzip option
v1.14 - 28-May-96
No need anymore for a crontab-run shell script to add day names and week cleanups to the log.
Web page installed.
v1.6 - 03-May-96
Directory name changed to "~/.browser-history", was "~/.netscape-history" before...
v1.4 - 15-Feb-96
Arena implements supports too.
v1.1 - 01-Feb-96
First version, netscape only.


Back to table of contents