#!/usr/bin/perl # Copyright (C) 2005 Eric L. Wilhelm # license: GPL/Artistic use strict; use warnings; use Getopt::Helpful; use File::Spec::Functions qw(rel2abs); use File::Find; our $debug = 0; my @exclude = qw(\..*\.swp$ ~$); # list of regexes my $command; my $file; my $url; my $hopt = Getopt::Helpful->new( usage => 'CALLER [options] ', ['c|cmd=s', \$command, '', 'command to run for refresh'], ['f|file=s', \$file, '', 'file or directory to watch'], ['u|url=s', \$url, '', 'url to reload'], '+debug', '+help', ); $hopt->Get(); my @command = split(/ /, $command || ''); $file ||= ($command and -e $command[0]) ? $command[0] : shift(@ARGV); $file = rel2abs($file); # required for watching symlink to dir $url ||= shift(@ARGV); $debug and warn "c: '@command', f: '$file', u: '$url'\n"; my $child_pid; if(@command) { $child_pid = start_command(@command); } warn "hi"; # I'll launch this if I cannot find one running # Ask me later why it's a variable. I don't know yet. my $cmd = 'konqueror'; ($file and (-e $file)) or die "usage: $0 \n"; unless($url) { warn "that probably doesn't work yet\n"; # XXX something with a simple YAML mapping file # (dirname -> server path) # or, process apache configs (too fancy?) $file = $url; } use SGI::FAM; use IPC::Run qw(run); my @found; my $new = 0; for(0..10) { # limit of start attempts @found = find_url_owner($url); @found and last; warn "could not find browser for $url\n"; # try to start one defined(my $pid = fork()) or die "cannot fork $!"; unless($pid) { # child open(STDOUT, '>/dev/null'); open(STDERR, '>/dev/null'); exec($cmd, $url) or die "$!"; } else { # parent needs to know that it started... $new = 1; use POSIX qw(:sys_wait_h); use Time::HiRes qw(usleep); until(waitpid($pid, WNOHANG)) { # XXX this needs a limit! usleep(1_000_000); if(my $instance = ask_cmd("$cmd-$pid", $url)) { # XXX this is probably broken @found = ($instance); # XXX, Yeah. @found is unnecessary last; } } @found or die "no child"; } } @found or die; warn "found @found\n"; # XXX it would be really nice to be tab-agnostic here, but you're # basically stuck assuming that the html widget is also the # mainwindow... unless($new) { # raise it in case it is buried if(1) { # XXX why doesn't 'raise' work? dcop($found[0], 'konqueror-mainwindow#1', 'hide'); dcop($found[0], 'konqueror-mainwindow#1', 'show'); } else { dcop($found[0], 'konqueror-mainwindow#1', 'raise'); } # and reload dcop($found[0], 'konqueror-mainwindow#1', 'reload'); } # XXX depending on how smart your editor thinks it is, this may not work # so well. Vim will write into a symlink, but it does the silly juggle # thing when it has a real file. If your editor makes a "change" event, # that's good. Otherwise it's bad. Emacs makes two change events. # That doesn't make it twice as good. # update: sprinkle your .vimrc with: # nobackup # nowritebackup warn "watching $file\n"; my $fam = SGI::FAM->new(); if(-d $file) { # decend manually (add an option to prevent this?) find(sub { (-d $File::Find::name) and return(); ($File::Find::name =~ m#/\.svn/#) and return(); ($File::Find::name =~ m#/CVS/#) and return(); ## ($_ =~ m/^\./) and return(); # XXX needs and option? regex_filter($_, @exclude) and return(); ## warn "add watch to $File::Find::name\n"; $fam->monitor($File::Find::name); }, $file); } else { $fam->monitor($file); } while(1) { my $event = $fam->next_event; if ($@) { die "$0: ERROR: $@\nPossibly fam server died?\n"; } my $type = $event->type(); ## print "event: ", $event->type(), " (", $event->filename, ")", "\n"; my $file = $event->filename; if(regex_filter($file, @exclude)) { ## warn "skipping excluded $file\n"; next; } if($type eq 'change') { warn "$type to ", $event->filename, "\n"; if($command) { # XXX ugh. this opens so many problems. What if it takes # longer than 0.1 s for the server to start, etc? $child_pid or die "ack"; kill_command($child_pid); $child_pid = start_command(@command); usleep(500_000); } dcop($found[0], 'konqueror-mainwindow#1', 'reload'); } else { #warn "not handling event type: $type for ", $event->filename, "\n"; } } exit; ######################################################################## BEGIN { my $dcop = '/usr/bin/dcop'; sub find_url_owner { my ($url) = @_; foreach my $b (dcop('konqueror-*')) { ## print "found $b\n"; if(my $inst = ask_cmd($b, $url)) { return($inst); } } return(); } # end subroutine find_url_owner definition ######################################################################## sub ask_cmd { my ($cmd, $url) = @_; foreach my $w (dcop($cmd, 'html-widget*')) { ## print "found $w\n"; my %have = map({$_ => 1} dcop($cmd, $w, 'url')); $have{$url} and return($cmd); } return(); } # end subroutine ask_cmd definition ######################################################################## sub dcop { my @args = @_; my ($in, $out, $err); run([$dcop, @args], \$in, \$out, \$err); if($err) { warn "error: $err\n "; return(); } return(split(/\n/, $out)); } # end subroutine dcop definition ######################################################################## sub start_command { my (@command) = @_; defined(my $pid = fork()) or die "cannot fork $!"; unless($pid) { my $tried = 0; while($tried++ < 5) { exec(@command) or warn " oops $!"; usleep(250_000); # XXX better with a pipe open? } die "tried $tried times... giving up"; } else { return($pid); } } # end subroutine start_command definition ######################################################################## sub kill_command { my ($pid) = @_; kill(9, $pid); } # end subroutine kill_command definition ######################################################################## sub regex_filter { my ($item, @matches) = @_; foreach my $regex (@matches) { return(1) if($item =~ m/$regex/); } return(0); } # end subroutine regex_filter definition ######################################################################## } # vim:ts=2:sw=2:noet