diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a61b64aa --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS?=-Wall -O2 +LDADD?=$(shell pkg-config --cflags --libs x11 xinerama x11 xft) + +all: normal + +normal: + $(CC) $(CFLAGS) $(LDADD) $(LDFLAGS) -o simpleswitcher simpleswitcher.c + +debug: + $(CC) $(CFLAGS) -Wunused-parameter -g -DDEBUG $(LDADD) -o simpleswitcher-debug simpleswitcher.c + +clean: + rm -f simpleswitcher simpleswitcher-debug diff --git a/simpleswitcher.c b/simpleswitcher.c new file mode 100644 index 00000000..05bd0d99 --- /dev/null +++ b/simpleswitcher.c @@ -0,0 +1,937 @@ +/* simpleswitcher + +MIT/X11 License +Copyright (c) 2012 Sean Pringle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define NEAR(a,o,b) ((b) > (a)-(o) && (b) < (a)+(o)) +#define OVERLAP(a,b,c,d) (((a)==(c) && (b)==(d)) || MIN((a)+(b), (c)+(d)) - MAX((a), (c)) > 0) +#define INTERSECT(x,y,w,h,x1,y1,w1,h1) (OVERLAP((x),(w),(x1),(w1)) && OVERLAP((y),(h),(y1),(h1))) + +void* allocate(unsigned long bytes) +{ + void *ptr = malloc(bytes); + if (!ptr) + { + fprintf(stderr, "malloc failed!\n"); + exit(EXIT_FAILURE); + } + return ptr; +} +void* allocate_clear(unsigned long bytes) +{ + void *ptr = allocate(bytes); + memset(ptr, 0, bytes); + return ptr; +} +void* reallocate(void *ptr, unsigned long bytes) +{ + ptr = realloc(ptr, bytes); + if (!ptr) + { + fprintf(stderr, "realloc failed!\n"); + exit(EXIT_FAILURE); + } + return ptr; +} + +void catch_exit(int sig) +{ + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +// cli arg handling +int find_arg(int argc, char *argv[], char *key) +{ + int i; for (i = 0; i < argc && strcasecmp(argv[i], key); i++); + return i < argc ? i: -1; +} +char* find_arg_str(int argc, char *argv[], char *key, char* def) +{ + int i = find_arg(argc, argv, key); + return (i > 0 && i < argc-1) ? argv[i+1]: def; +} +int find_arg_opts(int argc, char *argv[], char *key, char **list, int count) +{ + char *s = find_arg_str(argc, argv, key, NULL); + int i; for (i = 0; i < count; i++) if (s && !strcasecmp(s, list[i])) return i; + return -1; +} +int find_arg_int(int argc, char *argv[], char *key, int def) +{ + int i = find_arg(argc, argv, key); + return (i > 0 && i < argc-1) ? strtol(argv[i+1], NULL, 10): def; +} + +unsigned int NumlockMask = 0; +Display *display; + +static int (*xerror)(Display *, XErrorEvent *); + +#define ATOM_ENUM(x) x +#define ATOM_CHAR(x) #x + +#define EWMH_ATOMS(X) \ + X(_NET_SUPPORTING_WM_CHECK),\ + X(_NET_CLIENT_LIST),\ + X(_NET_CLIENT_LIST_STACKING),\ + X(_NET_NUMBER_OF_DESKTOPS),\ + X(_NET_CURRENT_DESKTOP),\ + X(_NET_DESKTOP_GEOMETRY),\ + X(_NET_DESKTOP_VIEWPORT),\ + X(_NET_WORKAREA),\ + X(_NET_ACTIVE_WINDOW),\ + X(_NET_CLOSE_WINDOW),\ + X(_NET_MOVERESIZE_WINDOW),\ + X(_NET_WM_NAME),\ + X(_NET_WM_WINDOW_TYPE),\ + X(_NET_WM_WINDOW_TYPE_DESKTOP),\ + X(_NET_WM_WINDOW_TYPE_DOCK),\ + X(_NET_WM_WINDOW_TYPE_SPLASH),\ + X(_NET_WM_WINDOW_TYPE_UTILITY),\ + X(_NET_WM_WINDOW_TYPE_TOOLBAR),\ + X(_NET_WM_WINDOW_TYPE_MENU),\ + X(_NET_WM_WINDOW_TYPE_DIALOG),\ + X(_NET_WM_WINDOW_TYPE_NORMAL),\ + X(_NET_WM_STATE),\ + X(_NET_WM_STATE_MODAL),\ + X(_NET_WM_STATE_STICKY),\ + X(_NET_WM_STATE_MAXIMIZED_VERT),\ + X(_NET_WM_STATE_MAXIMIZED_HORZ),\ + X(_NET_WM_STATE_SHADED),\ + X(_NET_WM_STATE_SKIP_TASKBAR),\ + X(_NET_WM_STATE_SKIP_PAGER),\ + X(_NET_WM_STATE_HIDDEN),\ + X(_NET_WM_STATE_FULLSCREEN),\ + X(_NET_WM_STATE_ABOVE),\ + X(_NET_WM_STATE_BELOW),\ + X(_NET_WM_STATE_DEMANDS_ATTENTION),\ + X(_NET_WM_STATE_ADD),\ + X(_NET_WM_STATE_REMOVE),\ + X(_NET_WM_STATE_TOGGLE),\ + X(_NET_WM_STRUT),\ + X(_NET_WM_STRUT_PARTIAL),\ + X(_NET_WM_DESKTOP),\ + X(_NET_SUPPORTED) + +enum { EWMH_ATOMS(ATOM_ENUM), NETATOMS }; +const char *netatom_names[] = { EWMH_ATOMS(ATOM_CHAR) }; +Atom netatoms[NETATOMS]; + +#define ADD 1 +#define REMOVE 0 +#define TOGGLE 2 + +// X error handler +int oops(Display *d, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + ) return 0; + fprintf(stderr, "error: request code=%d, error code=%d\n", ee->request_code, ee->error_code); + return xerror(display, ee); +} + +// usable space on a monitor +typedef struct { + int x, y, w, h; + int l, r, t, b; +} workarea; + +// window lists +typedef struct { + Window *array; + void **data; + int len; +} winlist; + +winlist *cache_client; +winlist *cache_xattr; + +#define winlist_ascend(l,i,w) for ((i) = 0; (i) < (l)->len && (((w) = (l)->array[i]) || 1); (i)++) +#define winlist_descend(l,i,w) for ((i) = (l)->len-1; (i) >= 0 && (((w) = (l)->array[i]) || 1); (i)--) + +#define WINLIST 32 + +winlist* winlist_new() +{ + winlist *l = allocate(sizeof(winlist)); l->len = 0; + l->array = allocate(sizeof(Window) * (WINLIST+1)); + l->data = allocate(sizeof(void*) * (WINLIST+1)); + return l; +} +int winlist_append(winlist *l, Window w, void *d) +{ + if (l->len > 0 && !(l->len % WINLIST)) + { + l->array = reallocate(l->array, sizeof(Window) * (l->len+WINLIST+1)); + l->data = reallocate(l->data, sizeof(void*) * (l->len+WINLIST+1)); + } + l->data[l->len] = d; + l->array[l->len++] = w; + return l->len-1; +} +void winlist_empty(winlist *l) +{ + while (l->len > 0) free(l->data[--(l->len)]); +} +void winlist_free(winlist *l) +{ + winlist_empty(l); free(l->array); free(l->data); free(l); +} +void winlist_empty_2d(winlist *l) +{ + while (l->len > 0) winlist_free(l->data[--(l->len)]); +} +int winlist_find(winlist *l, Window w) +{ + // iterate backwards. theory is: windows most often accessed will be + // nearer the end. testing with kcachegrind seems to support this... + int i; Window o; winlist_descend(l, i, o) if (w == o) return i; + return -1; +} +int winlist_forget(winlist *l, Window w) +{ + int i, j; + for (i = 0, j = 0; i < l->len; i++, j++) + { + l->array[j] = l->array[i]; + l->data[j] = l->data[i]; + if (l->array[i] == w) { free(l->data[i]); j--; } + } + l->len -= (i-j); + return j != i ?1:0; +} + +#define CLIENTTITLE 100 +#define CLIENTCLASS 50 +#define CLIENTNAME 50 +#define CLIENTSTATE 10 + +// a managable window +typedef struct { + Window window, trans; + XWindowAttributes xattr; + char title[CLIENTTITLE], class[CLIENTCLASS], name[CLIENTNAME]; + int states; + Atom state[CLIENTSTATE], type; + workarea monitor; +} client; + +// allocate a pixel value for an X named color +unsigned int color_get(Display *d, const char *name) +{ + XColor color; + Colormap map = DefaultColormap(d, DefaultScreen(d)); + return XAllocNamedColor(d, map, name, &color, &color) ? color.pixel: None; +} + +// find mouse pointer location +int pointer_get(Window root, int *x, int *y) +{ + *x = 0; *y = 0; + Window rr, cr; int rxr, ryr, wxr, wyr; unsigned int mr; + if (XQueryPointer(display, root, &rr, &cr, &rxr, &ryr, &wxr, &wyr, &mr)) + { + *x = rxr; *y = ryr; + return 1; + } + return 0; +} + +// XGetWindowAttributes with caching +XWindowAttributes* window_get_attributes(Window w) +{ + int idx = winlist_find(cache_xattr, w); + if (idx < 0) + { + XWindowAttributes *cattr = allocate(sizeof(XWindowAttributes)); + if (XGetWindowAttributes(display, w, cattr)) + { + winlist_append(cache_xattr, w, cattr); + return cattr; + } + free(cattr); + return NULL; + } + return cache_xattr->data[idx]; +} + +// retrieve a property of any type from a window +int window_get_prop(Window w, Atom prop, Atom *type, int *items, void *buffer, int bytes) +{ + Atom _type; if (!type) type = &_type; + int _items; if (!items) items = &_items; + int format; unsigned long nitems, nbytes; unsigned char *ret = NULL; + memset(buffer, 0, bytes); + + if (XGetWindowProperty(display, w, prop, 0, bytes/4, False, AnyPropertyType, type, + &format, &nitems, &nbytes, &ret) == Success && ret && *type != None && format) + { + if (format == 8) memmove(buffer, ret, MIN(bytes, nitems)); + if (format == 16) memmove(buffer, ret, MIN(bytes, nitems * sizeof(short))); + if (format == 32) memmove(buffer, ret, MIN(bytes, nitems * sizeof(long))); + *items = (int)nitems; XFree(ret); + return 1; + } + return 0; +} + +// retrieve a text property from a window +// technically we could use window_get_prop(), but this is better for character set support +char* window_get_text_prop(Window w, Atom atom) +{ + XTextProperty prop; char *res = NULL; + char **list = NULL; int count; + if (XGetTextProperty(display, w, &prop, atom) && prop.value && prop.nitems) + { + if (prop.encoding == XA_STRING) + { + res = allocate(strlen((char*)prop.value)+1); + strcpy(res, (char*)prop.value); + } + else + if (XmbTextPropertyToTextList(display, &prop, &list, &count) >= Success && count > 0 && *list) + { + res = allocate(strlen(*list)+1); + strcpy(res, *list); + XFreeStringList(list); + } + } + if (prop.value) XFree(prop.value); + return res; +} + +int window_get_atom_prop(Window w, Atom atom, Atom *list, int count) +{ + Atom type; int items; + return window_get_prop(w, atom, &type, &items, list, count*sizeof(Atom)) && type == XA_ATOM ? items:0; +} + +void window_set_atom_prop(Window w, Atom prop, Atom *atoms, int count) +{ + XChangeProperty(display, w, prop, XA_ATOM, 32, PropModeReplace, (unsigned char*)atoms, count); +} + +int window_get_cardinal_prop(Window w, Atom atom, unsigned long *list, int count) +{ + Atom type; int items; + return window_get_prop(w, atom, &type, &items, list, count*sizeof(unsigned long)) && type == XA_CARDINAL ? items:0; +} + +// a ClientMessage +int window_send_message(Window target, Window subject, Atom atom, unsigned long protocol, unsigned long mask) +{ + XEvent e; memset(&e, 0, sizeof(XEvent)); + e.xclient.type = ClientMessage; + e.xclient.message_type = atom; e.xclient.window = subject; + e.xclient.data.l[0] = protocol; e.xclient.data.l[1] = CurrentTime; + e.xclient.send_event = True; e.xclient.format = 32; + int r = XSendEvent(display, target, False, mask, &e) ?1:0; + XFlush(display); + return r; +} + +// find the dimensions of the monitor displaying point x,y +void monitor_dimensions(Screen *screen, int x, int y, workarea *mon) +{ + memset(mon, 0, sizeof(workarea)); + mon->w = WidthOfScreen(screen); + mon->h = HeightOfScreen(screen); + + // locate the current monitor + if (XineramaIsActive(display)) + { + int monitors, i; + XineramaScreenInfo *info = XineramaQueryScreens(display, &monitors); + if (info) for (i = 0; i < monitors; i++) + { + if (INTERSECT(x, y, 1, 1, info[i].x_org, info[i].y_org, info[i].width, info[i].height)) + { + mon->x = info[i].x_org; mon->y = info[i].y_org; + mon->w = info[i].width; mon->h = info[i].height; + break; + } + } + XFree(info); + } +} + +// determine which monitor holds the active window, or failing that the mouse pointer +void monitor_active(Screen *screen, workarea *mon) +{ + Window root = RootWindow(display, XScreenNumberOfScreen(screen)); + + unsigned long id; Atom type; int count; + if (window_get_prop(root, netatoms[_NET_ACTIVE_WINDOW], &type, &count, &id, 1) + && type == XA_WINDOW && count > 0) + { + XWindowAttributes *attr = window_get_attributes(id); + monitor_dimensions(screen, attr->x, attr->y, mon); + return; + } + int x, y; + if (pointer_get(root, &x, &y)) + { + monitor_dimensions(screen, x, y, mon); + return; + } + monitor_dimensions(screen, 0, 0, mon); +} + +// _NET_WM_STATE_* +int client_has_state(client *c, Atom state) +{ + int i; for (i = 0; i < c->states; i++) if (c->state[i] == state) return 1; + return 0; +} + +// collect info on any window +// doesn't have to be a window we'll end up managing +client* window_client(Window win) +{ + if (win == None) return NULL; + int idx = winlist_find(cache_client, win); + if (idx >= 0) return cache_client->data[idx]; + + // if this fails, we're up that creek + XWindowAttributes *attr = window_get_attributes(win); + if (!attr) return NULL; + + client *c = allocate_clear(sizeof(client)); + c->window = win; + // copy xattr so we don't have to care when stuff is freed + memmove(&c->xattr, attr, sizeof(XWindowAttributes)); + XGetTransientForHint(display, win, &c->trans); + + c->states = window_get_atom_prop(win, netatoms[_NET_WM_STATE], c->state, CLIENTSTATE); + window_get_atom_prop(win, netatoms[_NET_WM_WINDOW_TYPE], &c->type, 1); + + if (c->type == None) c->type = (c->trans != None) + // trasients default to dialog + ? netatoms[_NET_WM_WINDOW_TYPE_DIALOG] + // non-transients default to normal + : netatoms[_NET_WM_WINDOW_TYPE_NORMAL]; + + char *name; + if ((name = window_get_text_prop(c->window, netatoms[_NET_WM_NAME])) && name) + { + snprintf(c->title, CLIENTTITLE, "%s", name); + free(name); + } + else + if (XFetchName(display, c->window, &name)) + { + snprintf(c->title, CLIENTTITLE, "%s", name); + XFree(name); + } + XClassHint chint; + if (XGetClassHint(display, c->window, &chint)) + { + snprintf(c->class, CLIENTCLASS, "%s", chint.res_class); + snprintf(c->name, CLIENTNAME, "%s", chint.res_name); + XFree(chint.res_class); XFree(chint.res_name); + } + monitor_dimensions(c->xattr.screen, c->xattr.x, c->xattr.y, &c->monitor); + winlist_append(cache_client, c->window, c); + return c; +} + +#define MENUXFTFONT "mono-14" +#define MENUWIDTH 50 +#define MENULINES 25 +#define MENUFG "#cccccc" +#define MENUBG "#222222" +#define MENUHLFG "#ffffff" +#define MENUHLBG "#005577" + +char *config_menu_font, *config_menu_fg, *config_menu_bg, *config_menu_hlfg, *config_menu_hlbg; +unsigned int config_menu_width, config_menu_lines, config_focus_mode, config_raise_mode, config_window_placement; + +// built-in filterable popup menu list +struct localmenu { + Window window; + GC gc; + Pixmap canvas; + XftFont *font; + XftColor *color; + XftDraw *draw; + XftColor fg, bg, hlfg, hlbg; + unsigned long xbg; + char **lines, **filtered; + int done, max_lines, num_lines, input_size, line_height; + int current, width, height, horz_pad, vert_pad, offset; + char *input, *selected, *manual; + XIM xim; + XIC xic; +}; + +// redraw the popup menu window +void menu_draw(struct localmenu *my) +{ + int i, n; + + // draw text input bar + XftDrawRect(my->draw, &my->bg, 0, 0, my->width, my->height); + XftDrawStringUtf8(my->draw, &my->fg, my->font, my->horz_pad, my->vert_pad+my->line_height-my->font->descent, (unsigned char*)my->input, strlen(my->input)); + + // filter lines by current input text + memset(my->filtered, 0, sizeof(char*) * (my->num_lines+1)); + for (i = 0, n = 0; my->lines[i]; i++) + { + if (!my->offset || strcasestr(my->lines[i], my->input)) + my->filtered[n++] = my->lines[i]; + } + // vertical bounds of highlight bar + my->current = MAX(0, MIN(my->current, n-1)); + for (i = 0; my->filtered[i]; i++) + { + XftColor fg = my->fg; + // vertical position of *top* of current line + int y = my->vert_pad+(my->line_height*(i+1)); + // http://en.wikipedia.org/wiki/Typeface#Font_metrics + int font_baseline = y + my->line_height - my->font->descent -1; + // are we highlighting this line? + if (i == my->current) + { + fg = my->hlfg; + XftDrawRect(my->draw, &my->hlbg, my->horz_pad, y, my->width-(my->horz_pad*2), my->line_height); + } + XftDrawStringUtf8(my->draw, &fg, my->font, my->horz_pad, font_baseline, (unsigned char*)my->filtered[i], strlen(my->filtered[i])); + } + // double buffering + XCopyArea(display, my->canvas, my->window, my->gc, 0, 0, my->width, my->height, 0, 0); +} + +// handle popup menu text input for filtering +void menu_key(struct localmenu *my, XEvent *ev) +{ + char pad[32]; KeySym key; Status stat; + int len = XmbLookupString(my->xic, &ev->xkey, pad, sizeof(pad), &key, &stat); + if (stat == XBufferOverflow) return; + pad[len] = 0; + + key = XkbKeycodeToKeysym(display, ev->xkey.keycode, 0, 0); + + if (key == XK_Escape) + my->done = 1; + else + if (key == XK_BackSpace) + { + if (my->offset > 0) + my->input[--(my->offset)] = 0; + } + else + if (key == XK_Up) + my->current = (my->current == 0 ? my->max_lines-1: my->current-1); + else + if (key == XK_Down || key == XK_Tab) + my->current = (my->current == my->max_lines-1 ? 0: my->current+1); + else + if (key == XK_Return) + { + if (my->filtered[my->current]) + my->selected = my->filtered[my->current]; + else + if (my->manual) + strcpy(my->manual, my->input); + my->done = 1; + } + else + if (!iscntrl(*pad) && my->offset < my->input_size-1) + { + my->input[my->offset++] = *pad; + my->input[my->offset] = 0; + } + menu_draw(my); +} + +// take over keyboard for popup menu +int menu_grab(struct localmenu *my) +{ + int i; + for (i = 0; i < 1000; i++) + { + if (XGrabKeyboard(display, my->window, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) + return 1; + usleep(1000); + } + return 0; +} + +// menu +int menu(Window root, char **lines, char *manual) +{ + int i, l, scr; + struct localmenu _my, *my = &_my; + + XWindowAttributes *attr = window_get_attributes(root); + workarea mon; monitor_active(attr->screen, &mon); + scr = XScreenNumberOfScreen(attr->screen); + + // this never fails, afaics. we get some sort of font, no matter what + my->font = XftFontOpenName(display, scr, config_menu_font); + XftColorAllocName(display, DefaultVisual(display, scr), DefaultColormap(display, scr), config_menu_fg, &my->fg); + XftColorAllocName(display, DefaultVisual(display, scr), DefaultColormap(display, scr), config_menu_bg, &my->bg); + XftColorAllocName(display, DefaultVisual(display, scr), DefaultColormap(display, scr), config_menu_hlfg, &my->hlfg); + XftColorAllocName(display, DefaultVisual(display, scr), DefaultColormap(display, scr), config_menu_hlbg, &my->hlbg); + my->line_height = my->font->ascent + my->font->descent +2; // +2 pixel extra line spacing + + for (l = 0, i = 0; lines[i]; i++) l = MAX(l, strlen(lines[i])); + + my->lines = lines; + my->num_lines = i; + my->max_lines = MIN(config_menu_lines, my->num_lines); + my->input_size = MAX(l, 100); + my->filtered = allocate_clear(sizeof(char*) * (my->num_lines+1)); + my->input = allocate_clear((my->input_size+1)*3); // utf8 in copied line + my->current = 0; // index of currently highlighted line + my->offset = 0; // length of text in input buffer + my->done = 0; // bailout flag + my->horz_pad = 5; // horizontal padding + my->vert_pad = 5; // vertical padding + my->width = (mon.w/100)*config_menu_width; + my->height = ((my->line_height) * (my->max_lines+1)) + (my->vert_pad*2); + my->xbg = color_get(display, config_menu_bg); + my->selected = NULL; + my->manual = manual; + + int x = mon.x + ((mon.w - my->width)/2); + int y = mon.y + (mon.h/2) - (my->height/2); + + my->window = XCreateSimpleWindow(display, root, x, y, my->width, my->height, 0, my->xbg, my->xbg); + // make it an unmanaged window + window_set_atom_prop(my->window, netatoms[_NET_WM_STATE], &netatoms[_NET_WM_STATE_ABOVE], 1); + window_set_atom_prop(my->window, netatoms[_NET_WM_WINDOW_TYPE], &netatoms[_NET_WM_WINDOW_TYPE_DOCK], 1); + XSelectInput(display, my->window, ExposureMask|KeyPressMask); + + // drawing environment + my->gc = XCreateGC(display, my->window, 0, 0); + my->canvas = XCreatePixmap(display, root, my->width, my->height, DefaultDepth(display, scr)); + my->draw = XftDrawCreate(display, my->canvas, DefaultVisual(display, scr), DefaultColormap(display, scr)); + + // input keymap->charmap handling + my->xim = XOpenIM(display, NULL, NULL, NULL); + my->xic = XCreateIC(my->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, my->window, XNFocusWindow, my->window, NULL); + + menu_draw(my); + XMapRaised(display, my->window); + if (!menu_grab(my)) + { + fprintf(stderr, "cannot grab keyboard!\n"); + return my->max_lines; + } + menu_draw(my); + // main event loop + for(;!my->done;) + { + XEvent ev; + XNextEvent(display, &ev); + if (ev.type == Expose) + menu_draw(my); + else + if (ev.type == KeyPress) + menu_key(my, &ev); + } + free(my->filtered); + XftDrawDestroy(my->draw); + XFreeGC(display, my->gc); + XftFontClose(display, my->font); + XUngrabKeyboard(display, CurrentTime); + free(my->input); + + if (my->selected) + for (i = 0; my->lines[i]; i++) + if (my->lines[i] == my->selected) + return i; + return -1; +} + +#define ALLWINDOWS 1 +#define DESKTOPWINDOWS 2 + +#define FORK 1 +#define NOFORK 2 + +void run_switcher(Window root, int mode, int fmode) +{ + // TODO: this whole function is messy. build a nicer solution + char pattern[50], **list = NULL; + int i, classfield = 0, plen = 0, lines = 0; + unsigned long desktops = 0, current_desktop = 0; + Window w; client *c; + + // windows we actually display. may be slightly different to _NET_CLIENT_LIST_STACKING + // if we happen to have a window destroyed while we're working... + winlist *ids = winlist_new(); + + window_get_cardinal_prop(root, netatoms[_NET_CURRENT_DESKTOP], ¤t_desktop, 1); + + // find window list + Atom type; int nwins; unsigned long *wins = allocate_clear(sizeof(unsigned long) * 100); + if (window_get_prop(root, netatoms[_NET_CLIENT_LIST_STACKING], &type, &nwins, wins, 100 * sizeof(unsigned long)) + && type == XA_WINDOW) + { + // calc widths of fields + for (i = 0; i < nwins; i++) + { + if ((c = window_client(wins[i]))) + { + if (mode == DESKTOPWINDOWS) + { + unsigned long wmdesktop = 0; + window_get_cardinal_prop(c->window, netatoms[_NET_WM_DESKTOP], &wmdesktop, 1); + if (wmdesktop != current_desktop) continue; + } + classfield = MAX(classfield, strlen(c->class)); + winlist_append(ids, c->window, NULL); + } + } + + // build line sprintf pattern + if (mode == ALLWINDOWS) + { + window_get_cardinal_prop(root, netatoms[_NET_NUMBER_OF_DESKTOPS], &desktops, 1); + plen += sprintf(pattern+plen, "%%-%ds ", desktops < 10 ? 1: 2); + } + plen += sprintf(pattern+plen, "%%-%ds %%s", MAX(5, classfield)); + list = allocate_clear(sizeof(char*) * (ids->len+1)); lines = 0; + + // build the actual list + winlist_ascend(ids, i, w) + { + if ((c = window_client(w))) + { + // final line format + unsigned long wmdesktop; char desktop[5]; desktop[0] = 0; + char *line = allocate(strlen(c->title) + strlen(c->class) + classfield + 50); + if (mode == ALLWINDOWS) + { + // find client's desktop. this is zero-based, so we adjust by since most + // normal people don't think like this :-) + window_get_cardinal_prop(c->window, netatoms[_NET_WM_DESKTOP], &wmdesktop, 1); + if (wmdesktop < 0xFFFFFFFF) sprintf(desktop, "%d", (int)wmdesktop+1); + sprintf(line, pattern, desktop, c->class, c->title); + } + else sprintf(line, pattern, c->class, c->title); + list[lines++] = line; + } + } + if (fmode == NOFORK || !fork()) + { + // we fork because it's technically possible to have multiple window + // lists up at once on a zaphod multihead X setup. + // this also happens to isolate the Xft font stuff in a child process + // that gets cleaned up every time. that library shows some valgrind + // strangeness... + display = XOpenDisplay(0); + XSync(display, True); + int n = menu(root, list, NULL); + if (n >= 0 && list[n]) + { + window_send_message(root, ids->array[n], netatoms[_NET_ACTIVE_WINDOW], 2, // 2 = pager + SubstructureNotifyMask | SubstructureRedirectMask); + } + exit(EXIT_SUCCESS); + } + for (i = 0; i < lines; i++) free(list[i]); + free(list); + } + free(wins); + winlist_free(ids); +} + +unsigned int all_windows_modmask; KeySym all_windows_keysym; +unsigned int desktop_windows_modmask; KeySym desktop_windows_keysym; + +// KeyPress event +void handle_keypress(XEvent *ev) +{ + KeySym key = XkbKeycodeToKeysym(display, ev->xkey.keycode, 0, 0); + + if ((all_windows_modmask == AnyModifier || ev->xkey.state & all_windows_modmask) && key == all_windows_keysym) + run_switcher(ev->xany.window, ALLWINDOWS, FORK); + + if ((desktop_windows_modmask == AnyModifier || ev->xkey.state & desktop_windows_modmask) && key == desktop_windows_keysym) + run_switcher(ev->xany.window, DESKTOPWINDOWS, FORK); +} + +// convert a Mod+key arg to mod mask and keysym +void parse_key(char *combo, unsigned int *mod, KeySym *key) +{ + unsigned int modmask = 0; + if (strcasestr(combo, "shift")) modmask |= ShiftMask; + if (strcasestr(combo, "control")) modmask |= ControlMask; + if (strcasestr(combo, "mod1")) modmask |= Mod1Mask; + if (strcasestr(combo, "mod2")) modmask |= Mod2Mask; + if (strcasestr(combo, "mod3")) modmask |= Mod3Mask; + if (strcasestr(combo, "mod4")) modmask |= Mod4Mask; + if (strcasestr(combo, "mod5")) modmask |= Mod5Mask; + *mod = modmask ? modmask: AnyModifier; + + char i = strlen(combo); + while (i > 0 && !strchr("-+", combo[i-1])) i--; + + KeySym sym = XStringToKeysym(combo+i); + if (sym == NoSymbol || (!modmask && (strchr(combo, '-') || strchr(combo, '+')))) + { + fprintf(stderr, "sorry, cannot understand key combination: %s\n", combo); + exit(EXIT_FAILURE); + } + *key = sym; +} + +// bind a key combination on a root window, compensating for Lock* states +void grab_key(Window root, unsigned int modmask, KeySym key) +{ + KeyCode keycode = XKeysymToKeycode(display, key); + XUngrabKey(display, keycode, AnyModifier, root); + + if (modmask != AnyModifier) + { + // bind to combinations of mod and lock masks, so caps and numlock don't confuse people + XGrabKey(display, keycode, modmask, root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(display, keycode, modmask|LockMask, root, True, GrabModeAsync, GrabModeAsync); + if (NumlockMask) + { + XGrabKey(display, keycode, modmask|NumlockMask, root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(display, keycode, modmask|NumlockMask|LockMask, root, True, GrabModeAsync, GrabModeAsync); + } + } + else + { + // nice simple single key bind + XGrabKey(display, keycode, AnyModifier, root, True, GrabModeAsync, GrabModeAsync); + } +} + +int main(int argc, char *argv[]) +{ + int i, j, scr; + + // catch help request + if (find_arg(argc, argv, "-help") >= 0 + || find_arg(argc, argv, "--help") >= 0 + || find_arg(argc, argv, "-h") >= 0) + { + fprintf(stderr, "See the man page or visit http://github.com/seanpringle/simpleswitcher\n"); + return EXIT_FAILURE; + } + if(!(display = XOpenDisplay(0))) + { + fprintf(stderr, "cannot open display!\n"); + return EXIT_FAILURE; + } + signal(SIGCHLD, catch_exit); + XSync(display, False); xerror = XSetErrorHandler(oops); XSync(display, False); + + // determine numlock mask so we can bind on keys with and without it + XModifierKeymap *modmap = XGetModifierMapping(display); + for (i = 0; i < 8; i++) + for (j = 0; j < (int)modmap->max_keypermod; j++) + if (modmap->modifiermap[i*modmap->max_keypermod+j] == XKeysymToKeycode(display, XK_Num_Lock)) + NumlockMask = (1<= 0) + { + run_switcher(RootWindow(display, DefaultScreen(display)), ALLWINDOWS, NOFORK); + exit(EXIT_SUCCESS); + } + if (find_arg(ac, av, "-dnow") >= 0) + { + run_switcher(RootWindow(display, DefaultScreen(display)), DESKTOPWINDOWS, NOFORK); + exit(EXIT_SUCCESS); + } + // in background mode from here on + + // key combination to display all windows from all desktops + parse_key(find_arg_str(ac, av, "-key", "F12"), &all_windows_modmask, &all_windows_keysym); + + // key combination to display only window on the current desktop + parse_key(find_arg_str(ac, av, "-dkey", "F11"), &desktop_windows_modmask, &desktop_windows_keysym); + + // bind key combos + for (scr = 0; scr < ScreenCount(display); scr++) + { + Window root = RootWindow(display, scr); + grab_key(root, all_windows_modmask, all_windows_keysym); + grab_key(root, desktop_windows_modmask, desktop_windows_keysym); + } + + XEvent ev; + for (;;) + { + // caches only live for a single event + winlist_empty(cache_xattr); + winlist_empty(cache_client); + + // block and wait for something + XNextEvent(display, &ev); + if (ev.xany.window == None) continue; + + if (ev.type == KeyPress) handle_keypress(&ev); + } + return EXIT_SUCCESS; +} \ No newline at end of file