/*\ ||| Written By Fredrik Hübinette ||| All rights reserved. No warrenties, use at your own risk. ||| This source is distributed under the GNU GENERAL PUBLIC LICENCE, ||| see the file "COPYING" for more information. \*/ /* * Note to self: * * Filter functionality for next version: * filter: pnmtogif "$file" "$outfile" * * implementation: * o let plugger register a property notification * handler for the plugin window. * o the child will process the data into temporary files * and send the url back to plugger by setting the property * o the child then waits around for a while and deletes * the files afterwards. * o Make sure that plugger doesn't kill the child, then it * cannot cleanup the temporary files. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugger.h" enum GobbleState { g_ignored = 0, g_withdraw, g_leader, g_reparent, }; #define WINDOW ((Window) windata.window) Display *display=0; NPWindow windata; int flags; int repeats; char *winname; char *command; char *file; int waitfd; int pid=0; int loops; /* Number of loops executed by current invocation */ int do_unlock=0; enum GobbleState gobble_state = g_ignored; static XWindowAttributes wattr; static int xaspect; static int yaspect; static Window victim=0; static Window old_parent=0; static Atom lock_property; struct timeval lock_timeout; int wpidpipe[2]; #define FEND(X,Y) (strchr(X,Y)?strchr(X,Y):(X)+strlen(X)) #define xalloc malloc #define xfree free /* * Functions below are called by the child process */ static int childpid=-1; int error_handler(Display *dpy, XErrorEvent *err) { #ifdef DEBUG char buffer[999]; XGetErrorText(dpy, err->error_code, buffer, sizeof(buffer)); D("!!!ERROR_HANDLER!!!\n"); D("Error: %s\n",buffer); #endif return 0; } Display *dpy() { if(display) return display; display = XOpenDisplay(getenv("DISPLAY")); XSetErrorHandler(error_handler); D("display=%x\n",display); return display; } #define MAX_IGNORED 3000 static int windows_found=0; static Window ignored[MAX_IGNORED]; int inital_win_cb(Window w) { if(windows_found < MAX_IGNORED) ignored[windows_found++]=w; return 0; } int win_cb(Window w) { int q; for(q=0;q0) { for (e=0;eflags = (leader_change->flags | WindowGroupHint); leader_change->window_group = wattr.root; XSetWMHints(dpy(),victim,leader_change); XFree(leader_change); } gobble_state = g_reparent; case g_reparent: D("Reparenting window %x into %x\n",victim,WINDOW); XReparentWindow (dpy(), victim, WINDOW, 0, 0); D("placewindow done\n"); } return 1; } /* X11 locking mechanism */ static void my_xlockwindow(Display *dpy, Window win, Atom prop) { char buffer[1024]; struct timeval tv, tv2; gettimeofday(&tv, 0); gethostname(buffer,sizeof(buffer)-1); sprintf(buffer+strlen(buffer),"-%d-%ld:%ld!", getpid(),(long)tv.tv_sec, (long)tv.tv_usec); tv.tv_sec+=10; /* Max lock wait time, 10 seconds */ D("Attempting to lock property = %d!\n",prop); D("My LOCKID='%s'\n",buffer); while(1) { int mode=PropModeAppend; int fmt; unsigned long nitems=0, bytes=0; unsigned char *property=0; Atom type; while(1) { property=0; XGetWindowProperty(dpy, win, prop, 0, 1, 0, /* Delete */ XA_STRING, &type, &fmt, &nitems, &bytes, &property); if(!property) break; D("Property already set, bytes_left=%d, waiting..\n",bytes); XFree(property); gettimeofday(&tv2, 0); if(tv2.tv_sec > tv.tv_sec) { D("Stealing lock....\n"); /* Time to steal the lock! */ mode=PropModeReplace; gettimeofday(&tv, 0); INC_TIME(&tv, 0, 10000 + (rand() & 16383)); break; } plugger_usleep((rand() & 16383) + 2000); /* 0.18383 seconds */ } D("XChangeProperty, mode=%d\n",mode); XChangeProperty(dpy, win, prop, XA_STRING, 8, mode, buffer, strlen(buffer)); D("Getting property, did we get the lock?\n"); property=0; XGetWindowProperty(dpy, win, prop, 0, sizeof(buffer), 0, /* Delete */ XA_STRING, &type, &fmt, &nitems, &bytes, &property); if(property) { int done=!strncmp(buffer,property,strlen(buffer)); D("LOCK: done=%d, bytes_left=%d\n",done,bytes); XFree(property); if(done) return; } } } static void my_xunlockwindow(Display *dpy, Window win, Atom prop) { D("!!!UNLOCKING!!!\n"); XDeleteProperty(dpy, win, prop); } void kill_child(int tmp) { if(do_unlock) my_xunlockwindow(dpy(), wattr.root, lock_property); D("GOT SIGTERM\n"); if(childpid != -1) plugger_kill_group(childpid, 0); _exit(0); } Bool AllXEventsPredicate(Display *dpy, XEvent *ev, char *arg) { return True; } int set_aspect(int x, int y) { int tmp; int ox=xaspect; int oy=yaspect; xaspect=x; yaspect=y; while(1) { D("computing... xaspect=%d yaspect=%d\n",xaspect,yaspect); tmp=gcd(xaspect, yaspect); xaspect/=tmp; yaspect/=tmp; if(xaspect>1 && yaspect>1 && (xaspect+yaspect>100)) { xaspect>>=1; yaspect>>=1; }else break; } D("xaspect=%d yaspect=%d\n",xaspect,yaspect); return ox != xaspect || oy != yaspect; } static void very_low_run(char **argv) { MY_SETPGRP(); #ifdef H_NOISY if(flags & H_NOISY) { int nl=open("/dev/null", O_RDONLY); D("Redirecting stdout and stderr\n"); dup2(nl,1); dup2(nl,2); close(nl); } #endif execvp(argv[0],argv); D("Execvp failed..%d\n",errno); exit(EX_UNAVAILABLE); } static int run(void) { char *argv[10]; char buffer[65536]; char *foo=buffer; if(pid || repeats <= 0) return; if(!WINDOW && !(flags & H_SWALLOW)) return; loops=1; /* If command expects data on stdin but data is actually in * a file, open the file and connect it to stdin. */ if(file && (flags & H_STREAM)) { int fd=open(file,O_RDONLY); dup2(fd,0); close(fd); D("Stream from file %s\n",file); } /* Hmm, what does H_REPEATCOUNT do again?? */ if(flags & H_REPEATCOUNT) loops=repeats; /* This application will play the data forever */ if(flags & H_LOOP) { D("Expecting application to loop.\n"); loops=0x7fffffff; } if(repeats >= 0x7fffffff) loops=0; if(flags & H_EXITS) loops=repeats; /* setup environment variable $file */ if(file && !(flags & H_STREAM)) { if((flags & H_MANY) && !(flags & H_REPEATCOUNT)) { int e; sprintf(foo,"file=%s",file); loops=MIN(repeats,10); for(e=0;e 0); } static void find_victim(Window checkme) { if(victim) return; if(!pid) return; D("Looking for victim... (%s)\n",winname); victim=winrecur(checkme ? checkme : wattr.root, checkme ? 2 : 3, winname, win_cb); if(victim) { /* We don't need to listen for new windows anymore */ XSelectInput(dpy(), wattr.root, 0); /* We can unlock the gobble lock */ if(do_unlock) { my_xunlockwindow(dpy(), wattr.root, lock_property); do_unlock=0; } /* Gather the window size information */ if(flags & H_MAXASPECT) { int tmp; XWindowAttributes ca; D("MAXASPECT\n"); XGetWindowAttributes(dpy(), victim, &ca); set_aspect(ca.width,ca.height); } old_parent=0; place_window(); } } static void check_x_events(void) { if(display) { XEvent ev; while(XCheckIfEvent(dpy(), &ev, AllXEventsPredicate, NULL)) { D("got event %s->%d\n", ev.xany.window == wattr.root ? "root" : ev.xany.window == victim ? "victim" : ev.xany.window == WINDOW ? "WINDOW" : "unknown", ev.type); if(ev.xany.window == wattr.root) { switch(ev.type) { case MapNotify: D("***MAPNOTIFY: %x\n",ev.xmap.window); find_victim(ev.xmap.window); break; case ConfigureNotify: D("***ConfigureNotify: %x\n",ev.xconfigure.window); find_victim(ev.xconfigure.window); break; } } else if(victim && ev.xany.window == victim) { switch(ev.type) { case UnmapNotify: D("UNMAPNOTIFY\n"); if(gobble_state == g_withdraw) gobble_state = g_leader; break; case MapNotify: D("MAPNOTIFY\n"); adjust_window_size(); break; case ReparentNotify: old_parent=ev.xreparent.parent; if(ev.xreparent.parent == WINDOW) { D("REPARENT NOTIFY to the right window\n"); XMapWindow (dpy(), victim); adjust_window_size(); }else{ D("REPARENT NOTIFY to some other window!\n"); gobble_state = g_withdraw; place_window(); } break; } } else if(WINDOW && ev.xany.window == WINDOW) { switch(ev.type) { case ConfigureRequest: if(set_aspect(ev.xconfigure.width, ev.xconfigure.height)) adjust_window_size(); break; case DestroyNotify: /* We probably need to find a new victim */ break; #if 0 case CreateNotify: checkme = ev.xcreatewindow.window; break; case ConfigureNotify: checkme = ev.xconfigure.window; break; #endif } } } } } static void check_waitfd_events(void) { static NPWindow wintmp; static int rptr=0; int tmp; Window oldwindow=WINDOW; D("Got waitfd data, old parent=%x waitfd=%d\n",WINDOW,waitfd); tmp=read(waitfd, ((char *)& wintmp)+ rptr, sizeof(wintmp) - rptr); if(tmp < 0) { if(errno == EINTR) return; D("Winddata read error, exiting\n"); _exit(1); } if(tmp == 0) { D("Winddata EOF, exiting\n"); _exit(1); } rptr+=tmp; if(rptr != sizeof(windata)) return; windata = wintmp; rptr=0; D("Got waitfd data, new parent=%x\n",WINDOW); if(WINDOW && WINDOW != oldwindow && dpy()) { XSelectInput(dpy(), WINDOW, SubstructureNotifyMask | SubstructureRedirectMask ); XSync(dpy(), 0); } if(victim) { adjust_window_size(); }else{ if((plugger_strstr(command,"$window") || plugger_strstr(command,"$hexwindow")) && oldwindow != WINDOW) { /* * Kill and re-start child * Hmm, what happens if we are streaming???? * We're not (because Mozilla can't) */ plugger_kill_group(pid,0); } } } static void check_waitpid_events(void) { int status; int tmp; D("Got waitpid data\n"); tmp=read(wpidpipe[0], ((char *)& status), sizeof(status)); if(tmp < 0) { if(errno == EINTR) return; D("waitpid fd read error, exiting\n"); _exit(1); } if(tmp == 0) { D("waitpid fd EOF, exiting\n"); _exit(1); } if(tmp != sizeof(status)) { D("waitpid fd insufficient data (Got %d bytes)\n", tmp); return; } D("wait done repeats=%d loops=%d\n",repeats, loops); repeats-=loops; loops=0; pid=0; childpid=0; D("wait done repeats=%d loops=%d\n",repeats, loops); if(!WIFEXITED(status)) { D("Process dumped core or something...\n"); exit(10); } if(WEXITSTATUS(status) && !(flags & H_IGNORE_ERRORS)) { D("Process exited with error code: %d\n",WEXITSTATUS(status)); exit(WEXITSTATUS(status)); } D("exited ok!\n"); } int main(int argc, char **argv) { fd_set fds; struct sigaction action; D("Helper started.....\n"); if(pipe(wpidpipe)< 0) { perror("pipe"); exit(1); } action.sa_handler=kill_child; sigfillset(&action.sa_mask); action.sa_flags=SA_RESTART; sigaction(SIGTERM, &action, 0); sigaction(SIGINT, &action, 0); sigaction(SIGTERM, &action, 0); action.sa_handler=sigchild; sigaction(SIGCHLD, &action, 0); if(argc < 2) { fprintf(stderr,"Plugger version " VERSION " helper application.\n" "Written by Fredrik Hubinette.\n" "Please see 'man plugger' for details.\n"); exit(1); } sscanf(argv[1],"%d,%d,%d,%d,%d,%d,%d,%d", &flags, &repeats, &waitfd, &windata.window, &windata.x, &windata.y, &windata.width, &windata.height); command=argv[2]; winname=getenv("winname"); file=getenv("file"); D("HELPER: %s %s %s %s %s\n", argv[0], argv[1], file, winname, command); while(1) { struct timeval tv; XEvent ev; int maxfd; Window checkme; FD_ZERO(&fds); FD_SET(waitfd,&fds); FD_SET(wpidpipe[0],&fds); maxfd=MAXIMUM(waitfd, wpidpipe[0]); tv.tv_sec=1; tv.tv_usec=0; if(display) { XFlush(dpy()); FD_SET(ConnectionNumber(dpy()),&fds); maxfd=MAXIMUM(maxfd, ConnectionNumber(dpy())); if(XPending(dpy())) tv.tv_sec=tv.tv_usec=0; } D("do sel dpy=%p\n",dpy()); if(gobble_state == g_reparent) { tv.tv_sec=0; tv.tv_usec=1000000/100; } D("SELECT IN\n"); if(select( maxfd + 1, &fds, NULL, NULL, &tv) > 0) { D("SELECT OUT\n"); if(FD_ISSET(waitfd, &fds)) check_waitfd_events(); if(FD_ISSET(wpidpipe[0], &fds)) check_waitpid_events(); }else{ D("SELECT ERROR\n"); } check_x_events(); if(do_unlock) { struct timeval tv2; gettimeofday(&tv2, 0); if(tv2.tv_sec > lock_timeout.tv_sec) { my_xunlockwindow(dpy(), wattr.root, lock_property); do_unlock=0; } } run(); } D("EXITING\n"); _exit(0); }