rofi/simpleswitcher.c
2013-07-20 12:28:03 +02:00

1153 lines
34 KiB
C

/**
* simpleswitcher
*
* MIT/X11 License
* Copyright (c) 2012 Sean Pringle <sean.pringle@gmail.com>
* Modified 2013 Qball Cow <qball@gmpclient.org>
*
* 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 <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xmd.h>
#include <X11/Xutil.h>
#include <X11/Xproto.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <err.h>
#include <X11/extensions/Xinerama.h>
#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)))
#define OPAQUE 0xffffffff
#define OPACITY "_NET_WM_WINDOW_OPACITY"
static void* allocate(unsigned long bytes)
{
void *ptr = malloc(bytes);
if (!ptr)
{
fprintf(stderr, "malloc failed!\n");
exit(EXIT_FAILURE);
}
return ptr;
}
static void* allocate_clear(unsigned long bytes)
{
void *ptr = allocate(bytes);
memset(ptr, 0, bytes);
return ptr;
}
static void* reallocate(void *ptr, unsigned long bytes)
{
ptr = realloc(ptr, bytes);
if (!ptr)
{
fprintf(stderr, "realloc failed!\n");
exit(EXIT_FAILURE);
}
return ptr;
}
static inline char **tokenize(const char *input)
{
if(input == NULL) return NULL;
char *saveptr = NULL, *token;
char **retv = NULL;
// First entry is always full (modified) stringtext.
int num_tokens = 1;
//First entry is string that is modified.
retv = malloc(2*sizeof(char*));
retv[0] = strdup(input);
retv[1] = NULL;
// Iterate over tokens.
for(
token = strtok_r(retv[0], " ", &saveptr);
token != NULL;
token = strtok_r(NULL, " ", &saveptr))
{
retv = realloc(retv, sizeof(char*)*(num_tokens+2));
retv[num_tokens+1] = NULL;
retv[num_tokens] = token;
num_tokens++;
}
return retv;
}
static inline void tokenize_free(char **ip)
{
if(ip == NULL) return;
if(ip[0])
free(ip[0]);
free(ip);
}
void catch_exit(int sig)
{
while (0 < waitpid(-1, NULL, WNOHANG));
}
int execsh(char *cmd)
{
// use sh for args parsing
return execlp("/bin/sh", "sh", "-c", cmd, NULL);
}
// execute sub-process
pid_t exec_cmd(char *cmd)
{
if (!cmd || !cmd[0]) return -1;
signal(SIGCHLD, catch_exit);
pid_t pid = fork();
if (!pid)
{
setsid();
execsh(cmd);
exit(EXIT_FAILURE);
}
return pid;
}
// cli arg handling
static int find_arg(const int argc, char * const argv[], const char * const key)
{
int i;
for (i = 0; i < argc && strcasecmp(argv[i], key); i++);
return i < argc ? i: -1;
}
static 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;
}
static 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; Screen *screen;
Window root;
int screen_id;
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];
// 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;
#define MENUXFTFONT "mono-14"
#define MENUWIDTH 50
#define MENULINES 25
#define MENUFG "#222222"
#define MENUBG "#f2f1f0"
#define MENUBGALT "#e9e8e7"
#define MENUHLFG "#ffffff"
#define MENUHLBG "#005577"
#define MENURETURN 1
#define MENUMODUP 2
#define MENUBC "black"
char *config_menu_font;
char *config_menu_fg;
char *config_menu_bg;
char *config_menu_hlfg;
char *config_menu_hlbg;
char *config_menu_bgalt;
char *config_menu_bc;
unsigned int config_menu_width;
unsigned int config_menu_lines;
unsigned int config_focus_mode;
unsigned int config_raise_mode;
unsigned int config_window_placement;
unsigned int config_menu_bw;
unsigned int config_window_opacity;
unsigned int config_zeltak_mode;
unsigned int config_i3_mode;
// allocate a pixel value for an X named color
static unsigned int color_get(const char *const name)
{
XColor color;
Colormap map = DefaultColormap(display, screen_id);
return XAllocNamedColor(display, 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;
}
int take_keyboard(Window w)
{
int i;
for (i = 0; i < 1000; i++)
{
if (XGrabKeyboard(display, w, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
return 1;
usleep(1000);
}
return 0;
}
void release_keyboard()
{
XUngrabKeyboard(display, CurrentTime);
}
// 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, Time time)
{
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] = time;
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(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 ALLWINDOWS 1
#define DESKTOPWINDOWS 2
unsigned int all_windows_modmask; KeySym all_windows_keysym;
unsigned int desktop_windows_modmask; KeySym desktop_windows_keysym;
// flags to set if we switch modes on the fly
int run_all_windows = 0, run_desktop_windows = 0, current_mode = 0;
Window main_window = None;
#include "textbox.c"
void menu_draw(textbox *text, textbox **boxes, int max_lines, int selected, char **filtered)
{
int i;
textbox_draw(text);
for (i = 0; i < max_lines; i++)
{
textbox_font(boxes[i], config_menu_font,
i == selected ? config_menu_hlfg: config_menu_fg,
i == selected ? config_menu_hlbg: config_menu_bg);
textbox_text(boxes[i], filtered[i] ? filtered[i]: "");
textbox_draw(boxes[i]);
}
}
/* Very bad implementation of tab completion.
* It will complete to the common prefix
*/
static int calculate_common_prefix(char **filtered, int max_lines)
{
int length_prefix = 0,j,found = 1;
if(filtered[0] != NULL) {
char *p = filtered[0];
do{
found = 1;
for(j=0; j < max_lines && filtered[j] != NULL; j++) {
if(filtered[j][length_prefix] == '\0' || filtered[j][length_prefix] != *p) {
if(found)
found=0;
break;
}
}
if(found)
length_prefix++;
p++;
}while(found );
}
return length_prefix;
}
int menu(char **lines, char **input, char *prompt, int selected, Time *time)
{
int line = -1, i, j, chosen = 0, aborted = 0;
workarea mon; monitor_active(&mon);
int num_lines = 0; for (; lines[num_lines]; num_lines++);
int max_lines = MIN(config_menu_lines, num_lines);
selected = MAX(MIN(num_lines-1, selected), 0);
int w = config_menu_width < 101 ? (mon.w/100)*config_menu_width: config_menu_width;
int x = mon.x + (mon.w - w)/2;
Window box;
XWindowAttributes attr;
// main window isn't explicitly destroyed in case we switch modes. Reusing it prevents flicker
if (main_window != None && XGetWindowAttributes(display, main_window, &attr))
{
box = main_window;
}
else
{
box = XCreateSimpleWindow(display, root, x, 0, w, 300, 1, color_get(config_menu_bc), color_get(config_menu_bg));
XSelectInput(display, box, ExposureMask);
// make it an unmanaged window
window_set_atom_prop(box, netatoms[_NET_WM_STATE], &netatoms[_NET_WM_STATE_ABOVE], 1);
//window_set_atom_prop(box, netatoms[_NET_WM_WINDOW_TYPE], &netatoms[_NET_WM_WINDOW_TYPE_DOCK], 1);
XSetWindowAttributes sattr; sattr.override_redirect = True;
XChangeWindowAttributes(display, box, CWOverrideRedirect, &sattr);
main_window = box;
// Set the WM_NAME
XStoreName(display, box, "simpleswitcher");
// Hack to set window opacity.
unsigned int opacity_set = (unsigned int)((config_window_opacity/100.0)* OPAQUE);
XChangeProperty(display, box, XInternAtom(display, OPACITY, False),
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *) &opacity_set, 1L);
}
// search text input
textbox *text = textbox_create(box, TB_AUTOHEIGHT|TB_EDITABLE, 5, 5, w-10, 1,
config_menu_font, config_menu_fg, config_menu_bg, "", prompt);
textbox_show(text);
int line_height = text->font->ascent + text->font->descent;
line_height += line_height/10;
// filtered list display
textbox **boxes = allocate_clear(sizeof(textbox*) * max_lines);
for (i = 0; i < max_lines; i++)
{
boxes[i] = textbox_create(box, TB_AUTOHEIGHT, 5, (i+1) * line_height + 5, w-10, 1,
config_menu_font, config_menu_fg, config_menu_bg, lines[i], NULL);
textbox_show(boxes[i]);
}
// filtered list
char **filtered = allocate_clear(sizeof(char*) * max_lines);
int *line_map = allocate_clear(sizeof(int) * max_lines);
int filtered_lines = max_lines;
int jin = 0;
for (i = 0; i < max_lines; i++)
{
if(config_i3_mode && strstr(lines[i], "i3bar") != NULL) continue;
filtered[jin] = lines[i];
line_map[jin] = i;
jin++;
}
// resize window vertically to suit
int h = line_height * (max_lines+1) + 8;
int y = mon.y + (mon.h - h)/2;
XMoveResizeWindow(display, box, x, y, w, h);
XMapRaised(display, box);
take_keyboard(box);
for (;;)
{
XEvent ev;
XNextEvent(display, &ev);
if (ev.type == Expose)
{
while (XCheckTypedEvent(display, Expose, &ev));
menu_draw(text, boxes, max_lines, selected, filtered);
}
else
if (ev.type == KeyPress)
{
while (XCheckTypedEvent(display, KeyPress, &ev));
if (time)
*time = ev.xkey.time;
int rc = textbox_keypress(text, &ev);
if (rc < 0)
{
chosen = 1;
break;
}
else
if (rc)
{
char **tokens = tokenize(text->text);
// input changed
for (i = 0, j = 0; i < num_lines && j < max_lines; i++)
{
int match = 1;
// Do a tokenized match.
if(tokens) for(int j = 1; match && tokens[j]; j++)
{
match = (strcasestr(lines[i], tokens[j]) != NULL);
}
// If each token was matched, add it to list.
if(match)
{
if(config_i3_mode && strstr(lines[i], "i3bar") != NULL) continue;
line_map[j] = i;
filtered[j++] = lines[i];
}
}
// Cleanup + bookkeeping.
filtered_lines = j;
selected = MAX(0, MIN(selected, j-1));
for (; j < max_lines; j++)
filtered[j] = NULL;
if(config_zeltak_mode && filtered_lines == 1){
chosen = 1;
break;
}
tokenize_free(tokens);
}
else
{
// unhandled key
KeySym key = XkbKeycodeToKeysym(display, ev.xkey.keycode, 0, 0);
if (key == XK_Escape
// pressing one of the global key bindings closes the switcher. this allows fast closing of the menu if an item is not selected
|| ((all_windows_modmask == AnyModifier || ev.xkey.state & all_windows_modmask) && key == all_windows_keysym)
|| ((desktop_windows_modmask == AnyModifier || ev.xkey.state & desktop_windows_modmask) && key == desktop_windows_keysym))
{
aborted = 1;
// pressing a global key binding that does not match the current mode switches modes on the fly. this allow fast flipping back and forth
if (
current_mode == DESKTOPWINDOWS &&
(all_windows_modmask == AnyModifier || ev.xkey.state & all_windows_modmask) &&
key == all_windows_keysym
)
{
run_all_windows = 1;
}
if (
current_mode == ALLWINDOWS &&
(desktop_windows_modmask == AnyModifier || ev.xkey.state & desktop_windows_modmask) &&
key == desktop_windows_keysym
)
{
run_desktop_windows = 1;
}
break;
}
else
// Up or Shift-Tab
if (key == XK_Up || (key == XK_Tab && ev.xkey.state & ShiftMask))
selected = selected ? MAX(0, selected-1): MAX(0, filtered_lines-1);
else
// Down or Tab
if (key == XK_Down || key == XK_Tab)
{
if(filtered_lines == 1) {
chosen = 1;
break;
}
int length_prefix = calculate_common_prefix(filtered, max_lines);
printf("Prefix: %s:%s:%d\n", filtered[0],text->text, length_prefix);
if(length_prefix && strncasecmp(filtered[0], text->text, length_prefix)) {
// Do not want to modify original string, so make copy.
// not eff..
char * str = strndup(filtered[0], length_prefix);
textbox_text(text, str);
textbox_cursor_end(text);
free(str);
}
else
selected = selected < filtered_lines-1 ? MIN(filtered_lines-1, selected+1): 0;
}
}
menu_draw(text, boxes, max_lines, selected, filtered);
}
}
release_keyboard();
if (chosen && filtered[selected])
line = line_map[selected];
if (line < 0 && !aborted && input)
*input = strdup(text->text);
textbox_free(text);
for (i = 0; i < max_lines; i++)
textbox_free(boxes[i]);
free(boxes);
free(filtered);
free(line_map);
return line;
}
#define FORK 1
#define NOFORK 2
void run_switcher(int mode, int fmode)
{
// TODO: this whole function is messy. build a nicer solution
// 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...
if (fmode == FORK)
{
if (fork()) return;
display = XOpenDisplay(0);
XSync(display, True);
}
do {
if (run_all_windows) mode = ALLWINDOWS;
if (run_desktop_windows) mode = DESKTOPWINDOWS;
run_all_windows = run_desktop_windows = 0;
current_mode = mode;
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();
if (!window_get_cardinal_prop(root, netatoms[_NET_CURRENT_DESKTOP], &current_desktop, 1))
current_desktop = 0;
// 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 = nwins-1; i > -1; i--)
{
if ((c = window_client(wins[i]))
&& !c->xattr.override_redirect
&& !client_has_state(c, netatoms[_NET_WM_STATE_SKIP_PAGER])
&& !client_has_state(c, netatoms[_NET_WM_STATE_SKIP_TASKBAR]))
{
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)
{
if (!window_get_cardinal_prop(root, netatoms[_NET_NUMBER_OF_DESKTOPS], &desktops, 1))
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 :-)
if (!window_get_cardinal_prop(c->window, netatoms[_NET_WM_DESKTOP], &wmdesktop, 1))
wmdesktop = 0xFFFFFFFF;
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;
}
}
char *input = NULL;
Time time;
int n = menu(list, &input, "> ", 1, &time);
if (n >= 0 && list[n])
{
if(config_i3_mode)
{
// Hack for i3.
char array[128];
snprintf(array,128,"i3-msg [id=\"%d\"] focus",(int)(ids->array[n]));
exec_cmd(array);
}
else
{
if (mode == ALLWINDOWS && isdigit(list[n][0]))
{
// TODO: get rid of strtol
window_send_message(root, root, netatoms[_NET_CURRENT_DESKTOP], strtol(list[n], NULL, 10)-1,
SubstructureNotifyMask | SubstructureRedirectMask, time);
XSync(display, False);
}
window_send_message(root, ids->array[n], netatoms[_NET_ACTIVE_WINDOW], 2, // 2 = pager
SubstructureNotifyMask | SubstructureRedirectMask, time);
}
}
else
// act as a launcher
if (input)
{
exec_cmd(input);
}
for (i = 0; i < lines; i++)
free(list[i]);
free(list);
}
free(wins);
winlist_free(ids);
}
while (run_all_windows || run_desktop_windows);
if (fmode == FORK)
exit(EXIT_SUCCESS);
}
// 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(ALLWINDOWS, FORK);
if ((desktop_windows_modmask == AnyModifier || ev->xkey.state & desktop_windows_modmask) && key == desktop_windows_keysym)
run_switcher(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(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;
// 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);
screen = DefaultScreenOfDisplay(display);
screen_id = DefaultScreen(display);
root = DefaultRootWindow(display);
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<<i);
XFreeModifiermap(modmap);
int ac = argc; char **av = argv;
cache_client = winlist_new();
cache_xattr = winlist_new();
// X atom values
for (i = 0; i < NETATOMS; i++) netatoms[i] = XInternAtom(display, netatom_names[i], False);
config_menu_width = find_arg_int(ac, av, "-width", MENUWIDTH);
config_menu_lines = find_arg_int(ac, av, "-lines", MENULINES);
config_menu_font = find_arg_str(ac, av, "-font", MENUXFTFONT);
config_menu_fg = find_arg_str(ac, av, "-fg", MENUFG);
config_menu_bg = find_arg_str(ac, av, "-bg", MENUBG);
config_menu_bgalt = find_arg_str(ac, av, "-bgalt", MENUBGALT);
config_menu_hlfg = find_arg_str(ac, av, "-hlfg", MENUHLFG);
config_menu_hlbg = find_arg_str(ac, av, "-hlbg", MENUHLBG);
config_menu_bc = find_arg_str(ac, av, "-bc", MENUBC);
config_menu_bw = find_arg_int(ac, av, "-bw", 1);
config_window_opacity = find_arg_int(ac, av, "-o", 100);
config_zeltak_mode = (find_arg(ac, av, "-zeltak") >= 0);
config_i3_mode = (find_arg(ac, av, "-i3") >= 0);
// flags to run immediately and exit
if (find_arg(ac, av, "-now") >= 0)
{
run_switcher(ALLWINDOWS, NOFORK);
exit(EXIT_SUCCESS);
}
if (find_arg(ac, av, "-dnow") >= 0)
{
run_switcher(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
grab_key(all_windows_modmask, all_windows_keysym);
grab_key(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;
}