#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "event.h" #include "print_event.h" #include "property.h" #include "util.h" #include "xatoms.h" #include "xutil.h" static Display *dpy; static Window w; static XineramaScreenInfo *screens = NULL; static int number_screens; static Window saved_focus = None; static Cursor cursor_cross; static Cursor cursor_vertical; static Cursor cursor_horizontal; static Cursor cursor_resize; static GC gc; struct corner_window { Window w; int corner; int x,y; } windows[4]; struct winlist { Window w; XWindowAttributes wa; XSizeHints size; long size_flag; struct winlist *next; }; static enum manager { no_window_manager, ehwm_manager, iccm_manager } manager = iccm_manager; static enum style { horizontal, vertical } style = horizontal; static struct winlist *head = NULL; static char *opt_display = NULL; static int opt_sync; static int opt_detach; static int opt_winsize = 2; static int opt_threshold = 45; static int opt_vert_frame = -1; static int opt_horiz_frame = -1; static int opt_bordersize = 1; static char *opt_corner = "nw"; static int opt_x = 0; static int opt_y = 0; static const unsigned long idle_mask = StructureNotifyMask | EnterWindowMask; static const unsigned long activated_mask = StructureNotifyMask | EnterWindowMask | PointerMotionMask; static const unsigned long choosing_mask = StructureNotifyMask | EnterWindowMask | ButtonReleaseMask | ButtonPressMask | Button1MotionMask; static const struct poptOption x11_options[] = { {"display", 0, POPT_ARG_STRING, &opt_display, 0, "which X11 display to connect to", "localhost:0.0" }, {"sync", 0, POPT_ARG_NONE, &opt_sync, 0, "Make X calls synchronous", NULL}, POPT_TABLEEND }; static const struct poptOption options[] = { { NULL, 0, POPT_ARG_INCLUDE_TABLE, (void *)x11_options, 0, "X Windows options", NULL }, { "detach", 'd', POPT_ARG_NONE, &opt_detach, 0, "Detach from terminal and run in background", NULL}, { "winsize", 0, POPT_ARG_INT, &opt_winsize, 0, "size of hidden window in pixels, useful when corners hard to hit", NULL}, { "threshold", 0, POPT_ARG_INT, &opt_threshold, 0, "number of pixels one must move to choose horizontal vs vertical", NULL}, { "vertical_frame", 0, POPT_ARG_INT, &opt_vert_frame, 0, "vertical space needed by window frames. will be autodetected with modern window managers.", NULL}, { "horizontal_frame", 0, POPT_ARG_INT, &opt_horiz_frame, 0, "horizontal space needed by window frames. will be autodetected with modern window managers.", NULL}, { "corner", 0, POPT_ARG_STRING, &opt_corner, 0, "which corner to use for activation (NE,NW,SE,SW)", NULL }, { "border_size", 0, POPT_ARG_INT, &opt_bordersize, 0, "How close we are willing to get to the edge of the work area.", NULL}, POPT_AUTOHELP POPT_TABLEEND }; static void fetch_screen_info(Display *dpy) { static XineramaScreenInfo the_screen; if (screens && (screens != &the_screen)) XFree(screens); screens = XineramaQueryScreens(dpy, &number_screens); if(!screens) { screens = &the_screen; number_screens = 1; screens->screen_number = 0; screens->x_org = 0; screens->y_org = 0; screens->width = WidthOfScreen(DefaultScreenOfDisplay(dpy)); screens->height = HeightOfScreen(DefaultScreenOfDisplay(dpy)); } //for(int i=0;i < number_screens; i++) // printf("Screen %i, (%i,%i), %ix%i\n", i, (int)screens[i].x_org, (int)screens[i].y_org, (int)screens[i].width, (int)screens[i].height); } static int which_screen(int x, int y) { XineramaScreenInfo *s = screens; for(int i=0;i < number_screens; i++,s++) if( x >= s->x_org && y >= s->y_org && x < s->x_org + s->width && y < s->y_org + s->height) return i; //printf("which_screen: couldn't find (%i,%i)\n", x,y); return 0; } static void set_focus(Display *dpy, Window w) { if(w == None) return; XEvent ev; memset(&ev,0,sizeof(ev)); ev.type = ClientMessage; ev.xclient.window = w; ev.xclient.display = dpy; ev.xclient.format = 32; ev.xclient.message_type = XA__NET_ACTIVE_WINDOW; ev.xclient.data.l[0] = 2; ev.xclient.data.l[1] = CurrentTime; ev.xclient.data.l[2] = 0; XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); } static void unmaximize_window(Display *dpy, Window w) { XEvent ev; memset(&ev,0,sizeof(ev)); ev.type = ClientMessage; ev.xclient.window = w; ev.xclient.display = dpy; ev.xclient.format = 32; ev.xclient.message_type = XA__NET_WM_STATE; ev.xclient.data.l[0] = XA__NET_WM_STATE_REMOVE; ev.xclient.data.l[1] = XA__NET_WM_STATE_MAXIMIZED_HORZ; ev.xclient.data.l[2] = XA__NET_WM_STATE_MAXIMIZED_VERT; ev.xclient.data.l[3] = XA__NET_WM_STATE_MAXIMIZED; XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); } // only needed for STRUT hack. #define MAX_CLIENTS 256 #define IN(i,x,w) (i >= x && i < x + w) static void get_viewarea(int screen_num, int *x, int *y, int *w, int *h) { XineramaScreenInfo *s = screens + screen_num; *x = s->x_org; *y = s->y_org; *w = s->width; *h = s->height; int strut_top = 0, strut_bottom = 0, strut_left = 0, strut_right = 0; long clients[MAX_CLIENTS]; memset(clients,0,sizeof(clients)); int num = get_property(dpy,DefaultRootWindow(dpy), XA__NET_CLIENT_LIST, XA_WINDOW, clients, sizeof(long)*MAX_CLIENTS, 0, 0); //printf("Num clients: %i\n", num/ sizeof(long)); for(int i = 0; i < num / sizeof(long); i++) { //printf("client: 0x%lx\n", (unsigned long)clients[i]); long net_strut[12]; memset(net_strut,0,sizeof(net_strut)); int n = get_property(dpy,clients[i], XA__NET_WM_STRUT_PARTIAL, XA_CARDINAL, net_strut, sizeof(long)*12, 0,0); if(!n) n = get_property(dpy,clients[i], XA__NET_WM_STRUT, XA_CARDINAL, net_strut, sizeof(long)*4, 0,0); if(!n) continue; if(n/sizeof(long) == 4) { //printf("short strut\n"); strut_left += net_strut[0]; strut_right += net_strut[1]; strut_top += net_strut[2]; strut_bottom += net_strut[3]; } else if (n / sizeof(long) == 12) { //printf("long strut\n"); if(IN(net_strut[4],*y,*h) || IN(net_strut[5], *y, *h)) strut_left += net_strut[0]; if(IN(net_strut[6],*y,*h) || IN(net_strut[7], *y, *h)) strut_right += net_strut[1]; if(IN(net_strut[8],*x,*w) || IN(net_strut[9], *x, *w)) strut_top += net_strut[2]; if(IN(net_strut[10],*x,*w) || IN(net_strut[11], *x, *w)) strut_bottom += net_strut[3]; } } *x += strut_left; *w -= (strut_left + strut_right); *y += strut_top; *h -= (strut_top + strut_bottom); } //struct corner_window * static void make_corner_window (Display *dpy, int corner) { unsigned long valuemask = CWEventMask | CWWinGravity | CWOverrideRedirect; XSetWindowAttributes att; att.event_mask = idle_mask; att.win_gravity = corner; att.override_redirect = True; unsigned x=0,y=0; switch(corner) { case NorthEastGravity: opt_x = DisplayWidth(dpy,DefaultScreen(dpy)) - 1; x = DisplayWidth(dpy,DefaultScreen(dpy)) - opt_winsize; break; case SouthEastGravity: opt_x = DisplayWidth(dpy,DefaultScreen(dpy)) - 1; x = DisplayWidth(dpy,DefaultScreen(dpy)) - opt_winsize; case SouthWestGravity: opt_y = DisplayHeight(dpy,DefaultScreen(dpy)) - 1; y = DisplayHeight(dpy,DefaultScreen(dpy)) - opt_winsize; case NorthWestGravity: default: ; } w = XCreateWindow(dpy,DefaultRootWindow(dpy),x,y, opt_winsize, opt_winsize, 0, 0, InputOnly, None, valuemask, &att); XMapRaised(dpy,w); } static void destroy_winlist(bool map_windows, bool free_list) { struct winlist *wl; struct winlist *next; for(wl = head;wl; wl = next) { if(map_windows) XMapWindow(dpy, wl->w); next = wl->next; if(free_list) free(wl); } if(free_list) head = NULL; } static int count_winlist(void) { int i; struct winlist *wl = head; for(i = 0;wl; wl = wl->next, i++); return i; } static void move_window(Display *dpy, Window w, int x, int y, int width, int height) { unmaximize_window(dpy,w); long extents[4]; memset(extents,0,sizeof(extents)); int ret = get_property(dpy,w, XA__NET_FRAME_EXTENTS, XA_CARDINAL, extents, sizeof(long)*4, sizeof(long)*4, 0); int vert_frame = opt_vert_frame; int horiz_frame = opt_horiz_frame; if(ret == -1) { if(vert_frame < 0) vert_frame = 2; if(horiz_frame < 0) horiz_frame = 2; } else { if(vert_frame < 0) vert_frame = extents[2] + extents[3]; if(horiz_frame < 0) horiz_frame = extents[0] + extents[1]; } width -= horiz_frame; height -= vert_frame; if(width < 1) width = 1; if(height < 1) height = 1; if(manager == no_window_manager) { XMoveResizeWindow(dpy,w, x, y, width, height); return; } XWindowChanges ch; ch.x = x; ch.y = y; ch.width = width; ch.height = height; ch.stack_mode = Above; XReconfigureWMWindow(dpy, w, DefaultScreen(dpy), CWX | CWY | CWWidth | CWHeight | CWStackMode , &ch); set_focus(dpy,w); } static void pack_windows(Display *dpy,struct winlist *head, enum style style, int x_org, int y_org, int width, int height) { if(width < 1) width = 1; if(height < 1) height = 1; if(!head) return; //set_focus(dpy, head->w); if(head->next == NULL) { //move_window(dpy,head->w, x_org, y_org, width - head->horiz_frame, height - head->vert_frame); move_window(dpy,head->w, x_org, y_org, width, height ); } else { //fprintf(stderr, "Laying down the law.\n"); int sz = style == horizontal ? width : height; int dsz = sz / count_winlist(); struct winlist *wl = head; int cv = sz; for(;wl;wl = wl->next) { cv -= dsz; if(style == horizontal) move_window(dpy,wl->w, x_org + cv, y_org + 0, dsz , height ); else move_window(dpy,wl->w, x_org + 0, y_org + cv, width , dsz ); } } struct winlist *wl = head; for(;wl;wl = wl->next) { if(wl->w == saved_focus) set_focus(dpy, wl->w); } XRaiseWindow(dpy,w); // just in case moving and resizing obscures it. } static void choose_window(Display *dpy, Window w) { if (w == None) { //printf("Skipping root window...\n"); return; } Window app_window = XmuClientWindow(dpy, w); //printf("Window: 0x%x ", (unsigned int)app_window); XWindowAttributes wa; XGetWindowAttributes(dpy, app_window, &wa); long type = None; get_property(dpy,app_window, XA__NET_WM_WINDOW_TYPE, XA_ATOM, &type, sizeof(long), 0, 0); if(wa.override_redirect || type == XA__NET_WM_WINDOW_TYPE_DOCK || type == XA__NET_WM_WINDOW_TYPE_DESKTOP) { //printf("Skipping special window...\n"); return; } //printf("Adding to group...\n"); struct winlist *wl = malloc(sizeof(struct winlist)); wl->w = app_window; wl->next = head; head = wl; wl->wa = wa; XGetWMNormalHints(dpy, wl->w, &wl->size, &wl->size_flag); if(manager == no_window_manager) { XLowerWindow(dpy, app_window); /* XWithdrawWindow(dpy, app_window, DefaultScreen(dpy)); XUnmapWindow(dpy, app_window); this is just in case, so the window doesn't get lost if a WM is in the process of starting XEvent ev; memset(&ev,0,sizeof(ev)); ev.type = UnmapNotify; ev.xmap.display = dpy; ev.xmap.event = DefaultRootWindow(dpy); ev.xmap.window = app_window; ev.xmap.override_redirect = False; XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); */ } else { XWindowChanges ch; memset(&ch,0,sizeof(ch)); ch.stack_mode = Below; ch.x = 10000; ch.y = 10000; XIconifyWindow(dpy, app_window, DefaultScreen(dpy)); /* XReconfigureWMWindow(dpy, app_window, DefaultScreen(dpy), CWX | CWY , &ch); XLowerWindow(dpy, app_window); XIconifyWindow(dpy, app_window, DefaultScreen(dpy)); XEvent ev; memset(&ev,0,sizeof(ev)); ev.type = ClientMessage; ev.xclient.window = app_window; ev.xclient.display = dpy; ev.xclient.format = 32; ev.xclient.message_type = XA_WM_CHANGE_STATE; ev.xclient.data.l[0] = IconicState; XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); */ } } static void fork_away(void) { int pid; fflush(stdout); fflush(stderr); if ((pid = fork()) < 0) { perror("fork"); fputs("Running in foreground...\n", stderr); return; } if (pid != 0) exit(0); chdir("/"); setsid(); close(0); close(1); close(2); } int main(int argc, const char **argv) { poptContext popt = poptGetContext("whaw", argc, argv, options, 0); int rc = poptGetNextOpt(popt); if(rc < -1) { /* an error occurred during option processing */ fprintf(stderr, "%s: %s\n", poptBadOption(popt, POPT_BADOPTION_NOALIAS), poptStrerror(rc)); fprintf(stderr, "use --help for a list of options\n"); exit(1); } poptFreeContext(popt); dpy = XOpenDisplay(opt_display); if(!dpy) { fprintf(stderr,"X11 says \"I can't open display '%s'\"\n", XDisplayName(opt_display)); exit(2); } XSynchronize(dpy, opt_sync); XGCValues gcv; gcv.subwindow_mode = IncludeInferiors; gcv.subwindow_mode = IncludeInferiors; gcv.function = GXxor; gcv.foreground = 0xFFFFFFFF; gcv.line_width = 2; gc = XCreateGC(dpy, DefaultRootWindow(dpy), GCLineWidth | GCForeground | GCFunction | GCSubwindowMode, &gcv); intern_xatoms(dpy); fetch_screen_info(dpy); cursor_cross = XCreateFontCursor(dpy,XC_fleur); cursor_horizontal = XCreateFontCursor(dpy,XC_sb_h_double_arrow); cursor_vertical = XCreateFontCursor(dpy,XC_sb_v_double_arrow); cursor_resize = XCreateFontCursor(dpy,XC_sizing); int corner = NorthEastGravity; if(!strcasecmp(opt_corner,"ne")) corner = NorthEastGravity; else if(!strcasecmp(opt_corner,"nw")) corner = NorthWestGravity; else if(!strcasecmp(opt_corner,"sw")) corner = SouthWestGravity; else if(!strcasecmp(opt_corner,"se")) corner = SouthEastGravity; make_corner_window(dpy,corner); if(opt_detach) fork_away(); struct event_state *event_state = create_event_state(); bool in_drag = false; while(1) { XEvent ev; XNextEvent(dpy,&ev); //print_event(ev); struct the_event *event; if(process_event(&ev, &event, event_state)) { for(;event;event = event->next) { switch(event->type) { case button_press: assert(!in_drag); if(event->button == 2 || ((event->button == 3) && !head)) { XUngrabPointer(dpy, event->time); destroy_winlist(true, true); set_focus(dpy, saved_focus); break; } if(event->button == 3) { destroy_winlist(true, false); int x,y,w,h; get_viewarea(which_screen(event->x, event->y), &x, &y, &w, &h); pack_windows(dpy, head, style, x + opt_bordersize, y + opt_bordersize , w - 2*opt_bordersize, h - 2*opt_bordersize); destroy_winlist(false, true); XUngrabPointer(dpy, event->time); break; } choose_window(dpy, ev.xbutton.subwindow); break; case drag_start: assert(!in_drag); in_drag = true; XGrabServer(dpy); case drag_update: ; int sx = MIN(event->start_x, event->x); int sy = MIN(event->start_y, event->y); int sw = abs(event->start_x - event->x); int sh = abs(event->start_y - event->y); MoveOutline(dpy, DefaultRootWindow(dpy), gc, sx, sy, sw, sh, 10, 0); break; case drag_end: MoveOutline(dpy, DefaultRootWindow(dpy), gc, 0, 0, 0, 0, 0, 0); XUngrabServer(dpy); in_drag = false; sx = MIN(event->start_x, event->x); sy = MIN(event->start_y, event->y); sw = abs(event->start_x - event->x); sh = abs(event->start_y - event->y); destroy_winlist(true, false); pack_windows(dpy, head, style, sx, sy, sw, sh); destroy_winlist(false, true); XUngrabPointer(dpy, event->time); break; } } } else switch(ev.type) { case ConfigureNotify: XRaiseWindow(dpy,w); break; case EnterNotify: if(ev.xcrossing.window != w || in_drag) break; XGrabPointer(dpy, DefaultRootWindow(dpy), True, activated_mask , GrabModeAsync, GrabModeAsync, None, cursor_cross, ev.xcrossing.time); long win = None; get_property(dpy,DefaultRootWindow(dpy), XA__NET_ACTIVE_WINDOW, XA_WINDOW, &win, sizeof(long), 0, 0); saved_focus = win; destroy_winlist(true, true); break; case MapNotify: XRaiseWindow(dpy,w); break; case MotionNotify: assert(!in_drag); if(abs(opt_y - (int)ev.xmotion.y) > opt_threshold) { XChangeActivePointerGrab(dpy, choosing_mask , cursor_vertical, ev.xmotion.time); style = vertical; break; } else if(abs(opt_x - (int)ev.xmotion.x) > opt_threshold) { XChangeActivePointerGrab(dpy, choosing_mask , cursor_horizontal, ev.xmotion.time); style = horizontal; break; } break; //default: //fprintf(stderr, "Unknown Event.\n"); } XRaiseWindow(dpy,w); } XCloseDisplay(dpy); return 0; } // attic /* Window root_return = DefaultRootWindow(dpy); Window parent_return = app_window; while(parent_return != root_return) { app_window = parent_return; Window *children_return; unsigned int nchildren_return; XQueryTree(dpy, app_window, &root_return, &parent_return, &children_return, &nchildren_return ); if(children_return) XFree(children_return); } printf("RRWindow: %x\n", (unsigned int)app_window); //XUnmapWindow(dpy, app_window, 10000,10000); // move it far out of the way */