/***************************************************************************** * Authors: Louis Bavoil * Fredrik Hübinette * * 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, USA. *****************************************************************************/ #include "mozplugger.h" static Display *display; static int pipe_fd; static int flags; static int repeats; static char *winname; static char *command; static char *file; static XWindowAttributes wattr; static Window victim; static Window old_parent; static NPWindow windata; #define WINDOW ((Window) windata.window) int error_handler(Display *dpy, XErrorEvent *err) { #ifdef DEBUG char buffer[1024]; XGetErrorText(dpy, err->error_code, buffer, sizeof(buffer)); D("!!!ERROR_HANDLER!!!\n"); D("Error: %s\n", buffer); #endif return 0; } /***************************************************************************** * Window resizing *****************************************************************************/ static int xaspect; static int yaspect; static int gcd(int a, int b) { if (a < b) return gcd(b,a); if (b == 0) return a; return gcd(b, a % b); } static int set_aspect(int x, int y) { int ox = xaspect; int oy = yaspect; int d = gcd(x, y); xaspect = x / d; yaspect = y / d; D("xaspect=%d yaspect=%d\n", xaspect, yaspect); return (ox != xaspect || oy != yaspect); } static void adjust_window_size(void) { static int old_x = 0; static int old_y = 0; static int old_w = 0; static int old_h = 0; int x = 0; int y = 0; int w = windata.width; int h = windata.height; int tmpw, tmph; if (!victim) return; if (!WINDOW) return; if (old_parent != WINDOW) return; if (flags & H_FILL) { D("Resizing window %x with FILL\n", victim); } else if (flags & H_MAXASPECT) { if (xaspect && yaspect) { D("Resizing window %x with MAXASPECT\n", victim); tmph = h / yaspect; tmpw = w / xaspect; if (tmpw < tmph) tmph = tmpw; tmpw = tmph * xaspect; tmph = tmph * yaspect; x = (w - tmpw) / 2; y = (h - tmph) / 2; w = tmpw; h = tmph; } else { D("Not resizing window\n"); return; } } D("New size: %dx%d+%d+%d\n", w, h, x, y); /* Hack to get X to actually resize the window when mozplugger * resizes it two times with the same dimensions. */ if (old_x == x && old_y == y && old_h == h && old_w == w) XMoveResizeWindow(display, victim, x, y, w, h+1); XMoveResizeWindow(display, victim, x, y, w, h); old_x = x; old_y = y; old_h = h; old_w = w; } /***************************************************************************** * Window reparenting *****************************************************************************/ static void change_leader() { XWMHints *leader_change; if ((leader_change = XGetWMHints(display, victim))) { leader_change->flags = (leader_change->flags | WindowGroupHint); leader_change->window_group = wattr.root; XSetWMHints(display,victim,leader_change); XFree(leader_change); } } static int reparent_window(void) { if (!victim) return 0; if (!WINDOW) return 0; if (old_parent == WINDOW) return 0; XSelectInput(display, victim, StructureNotifyMask); XSync(display, False); D("Changing leader of window %x\n", victim); change_leader(); D("Reparenting window %x into %x\n", victim, WINDOW); XReparentWindow(display, victim, WINDOW, 0, 0); D("reparent_window() done\n"); return 1; } static int is_viewable(Display *display, Window w) { XWindowAttributes a; if (!XGetWindowAttributes(display, w, &a)) { D("Could not get Window Attributes\n"); return 0; } if (a.map_state != IsViewable) { D("Invisible window\n"); return 0; } return 1; } static int my_strcmp(char *windowname, char *name) { if (!name) return 1; if (!windowname) return 1; switch (name[0]) { case '=': return strcmp(name+1, windowname); case '~': return strcasecmp(name+1, windowname); case '*': return strncasecmp(name+1, windowname, strlen(name)-1); default: return !strstr(windowname, name); } } static char check_window_name(Window w, char *name, int (*cb)(Window)) { char *windowname; XClassHint windowclass; char match; D("XFetchName\n"); if (XFetchName(display, w, &windowname)) { D("Winrecur, checking window NAME %x (%s != %s)\n", w, windowname, name); match = !my_strcmp(windowname, name); XFree(windowname); if (match) { return cb(w); } } D("XGetClassHint\n"); if (XGetClassHint(display, w, &windowclass)) { D("Winrecur, checking window CLASS %x (%s != %s)\n", w, windowclass.res_name, name); match = !my_strcmp(windowclass.res_name, name); XFree(windowclass.res_class); XFree(windowclass.res_name); if (match) { return cb(w); } } return 0; } static Window winrecur(Window from, int depth, char *name, int (*cb)(Window)) { Window root, parent; Window *children; int e, num_children; D("Winrecur, checking window %x (depth=%d)\n",from,depth); if (!(flags & H_HIDDEN) && !is_viewable(display, from)) return (Window)0; if (check_window_name(from, name, cb)) return from; if (!XQueryTree(display, from, &root, &parent, &children, &num_children)) { D("Querytree failed!!!\n"); return (Window)0; } if (depth > 0) { D("Num children = %d\n", num_children); for (e = 0; e < num_children; e++) { Window win = winrecur(children[e], depth-1, name, cb); if (win) { XFree(children); return (Window)win; } } } XFree(children); return (Window)0; } #define MAX_IGNORED 3000 static int windows_found = 0; static Window ignored[MAX_IGNORED]; int initial_win_cb(Window w) { if (windows_found < MAX_IGNORED) { ignored[windows_found++] = w; D("Ignoring window %x\n", w); } return 0; } /* Returns 1 if and only if w is a new window. */ int win_cb(Window w) { int i; for (i=0; i < windows_found; i++) { if (ignored[i] == w) return 0; } return 1; } void save_initial_windows() { windows_found = 0; winrecur(wattr.root, 3, winname, initial_win_cb); } static int setup_display() { char *displayname = getenv("DISPLAY"); D("setup_display(%s)\n", displayname); XSetErrorHandler(error_handler); if (!(display = XOpenDisplay(displayname))) return 1; if (!XGetWindowAttributes(display, WINDOW, &wattr)) return 1; D("display=%x\n", display); D("WINDOW=%x\n", WINDOW); D("rootwin=%x\n", wattr.root); D("setup_display() done\n"); return 0; } static void run_app(char **argv) { int nl; if (flags & H_NOISY) { D("Redirecting stdout and stderr\n"); if ((nl = open("/dev/null", O_RDONLY)) == -1) exit(EX_UNAVAILABLE); dup2(nl, 1); dup2(nl, 2); close(nl); } execvp(argv[0], argv); D("Execvp failed. (errno=%d)\n", errno); exit(EX_UNAVAILABLE); } static void find_victim(Window window) { if (!victim) { D("Looking for victim... (%s)\n", winname); victim = winrecur(window, 2, winname, win_cb); if (victim) { D("Found it!! Victim=%x\n", victim); /* We don't need to listen for new windows anymore */ XSelectInput(display, wattr.root, 0); if (flags & H_MAXASPECT) { XWindowAttributes ca; XGetWindowAttributes(display, victim, &ca); set_aspect(ca.width, ca.height); } old_parent = 0; reparent_window(); } } } static Bool AllXEventsPredicate(Display *dpy, XEvent *ev, char *arg) { return True; } static void check_x_events(void) { XEvent ev; static char mapped = 0; static char reparented = 0; while (XCheckIfEvent(display, &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 CreateNotify: D("***CreateNotify\n"); find_victim(ev.xcreatewindow.window); break; 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"); mapped = 0; break; case MapNotify: D("MAPNOTIFY\n"); mapped = 1; adjust_window_size(); break; case ReparentNotify: old_parent = ev.xreparent.parent; if (ev.xreparent.parent == WINDOW) { D("REPARENT NOTIFY to the right window\n"); adjust_window_size(); reparented = 1; } else { D("REPARENT NOTIFY to some other window!\n"); reparent_window(); reparented = 0; } break; } } else if (WINDOW && ev.xany.window == WINDOW) { switch (ev.type) { case ConfigureRequest: D("ConfigureRequest\n"); set_aspect(ev.xconfigure.width, ev.xconfigure.height); adjust_window_size(); break; } } } /* For Window Maker */ if (reparented && !mapped) { D("XMapWindow\n"); XMapWindow(display, victim); } } static void check_pipe_fd_events(void) { static NPWindow wintmp; int n; Window oldwindow = WINDOW; D("Got pipe_fd data, old parent=%x pipe_fd=%d\n", WINDOW, pipe_fd); n = read(pipe_fd, ((char *)& wintmp), sizeof(wintmp)); if (n < 0) { if (errno == EINTR) return; D("Winddata read error, exiting\n"); exit(EX_UNAVAILABLE); } if (n == 0) { D("Winddata EOF, exiting\n"); exit(EX_UNAVAILABLE); } if (n != sizeof(windata)) return; windata = wintmp; D("Got pipe_fd data, new parent=%x\n", WINDOW); if (WINDOW && WINDOW != oldwindow && display) { XSelectInput(display, WINDOW, SubstructureRedirectMask); XSync(display, False); } if (victim) { /* The window has been resized. */ adjust_window_size(); } } static void check_all_events() { struct timeval tv; int maxfd; fd_set fds; FD_ZERO(&fds); FD_SET(ConnectionNumber(display), &fds); FD_SET(pipe_fd, &fds); maxfd = MAX(ConnectionNumber(display), pipe_fd); tv.tv_sec = SELECT_TIMEOUT_SEC; tv.tv_usec = SELECT_TIMEOUT_USEC; D("SELECT IN\n"); if (select(maxfd + 1, &fds, NULL, NULL, &tv) > 0) { D("SELECT OUT\n"); if (FD_ISSET(pipe_fd, &fds)) check_pipe_fd_events(); } if (display) { check_x_events(); } } static void handle_app(int pid) { int status; if (flags & H_SWALLOW) { while (!waitpid(pid, &status, WNOHANG)) { check_all_events(); } } else { waitpid(pid, &status, 0); } if (flags & H_DAEMON) { exit(0); } if (!WIFEXITED(status)) { D("Process dumped core or something...\n"); exit(EX_UNAVAILABLE); } 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) { D("Helper started.....\n"); if (argc < 2) { fprintf(stderr,"MozPlugger version " VERSION " helper application.\n" "Written by Fredrik Hubinette and Louis Bavoil.\n" "Please see 'man mozplugger' for details.\n"); exit(1); } sscanf(argv[1],"%d,%d,%d,%lu,%d,%d,%d,%d", &flags, &repeats, &pipe_fd, (long unsigned int *) &windata.window, (int *)&windata.x, (int *)&windata.y, (int *)&windata.width, (int *)&windata.height); command = argv[2]; winname = getenv("winname"); file = getenv("file"); D("HELPER: %s %s %s %s\n", argv[0], argv[1], file, command); while (repeats > 0) { char *argv[10]; int loops = 1; int pid; /* This application will use the $repeat variable */ if (flags & H_REPEATCOUNT) loops = repeats; /* Expecting the application to loop */ if (flags & H_LOOP) loops = MAXINT; /* Create command line */ if (flags & H_CONTROLS) { argv[0] = getenv("controller"); argv[1] = command; argv[2] = NULL; } else { argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = command; argv[3] = NULL; } if (setup_display()) { D("setup_display() failed\n"); flags &=~ H_SWALLOW; } else { /* Has to be done before forking */ save_initial_windows(); XSelectInput(display, WINDOW, SubstructureRedirectMask); XSelectInput(display, wattr.root, SubstructureNotifyMask); XSync(display, False); } if ((pid = fork()) == -1) exit(EX_UNAVAILABLE); if (!pid) { XSelectInput(display, WINDOW, 0); XSelectInput(display, wattr.root, 0); D("Running %s\n", command); run_app(argv); } else { D("Waiting for pid=%d\n", pid); handle_app(pid); D("Wait done (repeats=%d, loops=%d)\n", repeats, loops); if (repeats < MAXINT) repeats -= loops; } } exit(0); }