diff --git a/action.c b/action.c index 379df21..5eb790d 100644 --- a/action.c +++ b/action.c @@ -149,3 +149,8 @@ void action_maximize_horz(void *data, int num, Client *cli) cli->maxh = client_toggle_state(cli, atoms[_NET_WM_STATE_MAXIMIZE_HORZ]); client_place_spot(cli, cli->spot, cli->monitor, 1); } + +void action_menu(void *data, int num, Client *cli) +{ + menu_create(current_spot, current_mon); +} \ No newline at end of file diff --git a/client.c b/client.c index 61af74f..1214237 100644 --- a/client.c +++ b/client.c @@ -74,7 +74,11 @@ Client* window_build_client(Window win) if (settings.title) for_monitors(i, m) for_spots(j) if (m->bars[j] && m->bars[j]->window == c->window) - { c->ours = 1; c->manage = 0; break; } + { c->ours = 1; c->bar = 1; c->manage = 0; break; } + + // detect our own menus + if (menu && menu->mb && menu->mb->window == c->window) + { c->ours = 1; c->menu = 1; c->manage = 0; } if (c->manage) { diff --git a/config.c b/config.c index 0697efb..c99bf7e 100644 --- a/config.c +++ b/config.c @@ -38,7 +38,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define CONFIG_UINT_NAMES CONFIG_BORDER "|" CONFIG_GAP "|" CONFIG_TITLE_ELLIPSIS #define CONFIG_STR_NAMES CONFIG_BORDER_BLUR "|" CONFIG_BORDER_FOCUS "|" CONFIG_BORDER_URGENT "|" CONFIG_TITLE "|" CONFIG_TITLE_BLUR "|" CONFIG_TITLE_FOCUS -#define CONFIG_ACTIONS "action_move_direction|action_focus_direction|action_move|action_focus|action_close|action_cycle|action_raise_nth|action_command|action_find_or_start|action_move_monitor|action_focus_monitor|action_fullscreen|action_maximize_vert|action_maximize_horz|action_maximize" +#define CONFIG_ACTIONS "action_move_direction|action_focus_direction|action_move|action_focus|action_close|action_cycle|action_raise_nth|action_command|action_find_or_start|action_move_monitor|action_focus_monitor|action_fullscreen|action_maximize_vert|action_maximize_horz|action_maximize|action_menu" void rtrim(char *str) @@ -366,6 +366,7 @@ configure() "action_maximize_vert", "action_maximize_horz", "action_maximize", + "action_menu", }; void *actions[] = { action_move, @@ -383,6 +384,7 @@ configure() action_maximize_vert, action_maximize_horz, action_maximize, + action_menu, }; for (i = 0; i < sizeof(names) / sizeof(char*); i++) { diff --git a/event.c b/event.c index fc338f5..446c73f 100644 --- a/event.c +++ b/event.c @@ -64,6 +64,7 @@ void configure_notify(XEvent *e) while (XCheckTypedEvent(display, ConfigureNotify, e)); ewmh_client_list(); update_bars(); + menu_update(); } client_free(c); } @@ -105,6 +106,7 @@ void map_notify(XEvent *e) client_free(a); ewmh_client_list(); update_bars(); + menu_update(); } client_free(c); } @@ -119,6 +121,7 @@ void unmap_notify(XEvent *e) } ewmh_client_list(); update_bars(); + menu_update(); } void key_press(XEvent *ev) @@ -128,6 +131,21 @@ void key_press(XEvent *ev) unsigned int state = e->state & ~(LockMask|NumlockMask); while (XCheckTypedEvent(display, KeyPress, ev)); + int i, j; Monitor *m; + for_monitors(i, m) for_spots(j) if (menu && menu->mb && menu->mb->window == e->window) + { + int rc = menubox_keypress(menu->mb, ev); + menubox_draw(menu->mb); + + if (rc == -2) + menu_close(); + + if (rc == -1) + menu_select(); + + return; + } + Binding *bind = NULL; for (int i = 0; i < settings.binding_count && !bind; i++) if (settings.bindings[i].key == key && (settings.bindings[i].mod == AnyModifier || settings.bindings[i].mod == state)) @@ -139,6 +157,7 @@ void key_press(XEvent *ev) bind->act(bind->data, bind->num, cli); client_free(cli); update_bars(); + menu_update(); } } @@ -197,6 +216,7 @@ void property_notify(XEvent *ev) { // root name appears in SPOT1 bar, for status etc update_bars(); + menu_update(); } else if (c && c->visible && c->manage) @@ -215,6 +235,7 @@ void expose(XEvent *e) { while (XCheckTypedEvent(display, Expose, e)); update_bars(); + menu_update(); } void any_event(XEvent *e) diff --git a/menu.c b/menu.c new file mode 100644 index 0000000..cc67134 --- /dev/null +++ b/menu.c @@ -0,0 +1,122 @@ +/* + +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. + +*/ + +void menu_update() +{ + if (menu->mb) + { + menubox_draw(menu->mb); + menubox_show(menu->mb); + } +} + +void menu_close() +{ + if (menu->mb) + { + menubox_free(menu->mb); + menu->mb = NULL; + + for (int i = 0; i < sizeof(menu->items)/sizeof(MenuMap); i++) + { + free(menu->items[i].name); + menu->items[i].name = NULL; + } + } +} + +void menu_select() +{ + int id; Client *c; + + if (menu->mb) + { + id = menubox_get_id(menu->mb); + if ((c = window_build_client(menu->items[id].window)) && c) + { + if (c->manage) + client_activate(c); + client_free(c); + } + menu_close(); + } +} + +int menu_sort(const void *a, const void *b) +{ + const MenuMap *ma = a; + const MenuMap *mb = b; + return strcmp(ma->name, mb->name); +} + +void menu_create(int spot, int mon) +{ + short x, y, w, h; + int i, n = 0; Client *c; + char *name = NULL; + + Monitor *m = &monitors[menu->monitor]; + + if (menu->mb) + menu_close(); + + x = m->spots[spot].x; + y = m->spots[spot].y; + w = m->spots[SPOT3].w/2; + h = 10; + + Menubox *mb = menubox_create(root, MB_AUTOHEIGHT|MB_AUTOWIDTH|MB_MINWIDTH, x, y, w, h, + settings.border, settings.title, settings.title_focus, settings.border_focus, settings.title_blur, settings.border_blur); + + menu->mb = mb; + menu->spot = spot; + menu->monitor = mon; + + for_windows(i,c) if (n < sizeof(menu->items)/sizeof(MenuMap) && c->manage && c->spot == spot && c->monitor == mon && (name = window_get_name(c->window)) && name) + { + menu->items[n].name = name; + menu->items[n].window = c->window; + n++; + } + + qsort(menu->items, n, sizeof(MenuMap), menu_sort); + + for (i = 0; i < n; i++) + menubox_add(mb, i, menu->items[i].name); + + x = (m->spots[spot].w - MIN(mb->w, m->spots[spot].w-100))/2; + y = (m->spots[spot].h - MIN(mb->h, m->spots[spot].h-100))/2; + + x += m->spots[spot].x; + y += m->spots[spot].y; + + menubox_moveresize(mb, x, y, w, h); + + menubox_key_down(mb); + menubox_draw(mb); + menubox_show(mb); + menubox_grab(mb); +} \ No newline at end of file diff --git a/menubox.c b/menubox.c new file mode 100644 index 0000000..c237b5e --- /dev/null +++ b/menubox.c @@ -0,0 +1,221 @@ +/* + +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 MB_AUTOHEIGHT 1<<0 +#define MB_AUTOWIDTH 1<<1 +#define MB_SELECTABLE 1<<2 +#define MB_MINWIDTH 1<<3 + +typedef struct { + unsigned long flags; + Window window, parent; + short x, y, w, h, line_height, padding; + char *font, *fg_focus, *bg_focus, *fg_blur, *bg_blur; + unsigned int box_count, box_active; + Textbox **boxes; + int *ids; + XIM xim; + XIC xic; +} Menubox; + +void menubox_moveresize(Menubox*, short, short, short, short); + +// Xft text box, optionally editable +Menubox* menubox_create(Window parent, unsigned long flags, short x, short y, short w, short h, short padding, char *font, char *fg_focus, char *bg_focus, char *fg_blur, char *bg_blur) +{ + Menubox *mb = calloc(1, sizeof(Menubox)); + mb->boxes = (Textbox**) calloc(1, sizeof(Textbox)); + mb->ids = (int*) calloc(1, sizeof(int)); + + mb->flags = flags; + mb->parent = parent; + mb->font = font; + mb->fg_focus = fg_focus; + mb->fg_blur = fg_blur; + mb->bg_focus = bg_focus; + mb->bg_blur = bg_blur; + mb->padding = padding; + mb->box_active = 0; + + mb->x = x; mb->y = y; mb->w = MAX(1, w); mb->h = MAX(1, h); + + XColor color; Colormap map = DefaultColormap(display, DefaultScreen(display)); + unsigned int cp = XAllocNamedColor(display, map, bg_blur, &color, &color) ? color.pixel: None; + + mb->window = XCreateSimpleWindow(display, mb->parent, mb->x, mb->y, mb->w, mb->h, 0, None, cp); + + // calc line height + Textbox *lb = textbox_create(mb->window, TB_AUTOHEIGHT, 0, 0, 0, 0, mb->font, mb->fg_blur, mb->bg_blur, "a", NULL); + mb->line_height = lb->h; textbox_free(lb); + + // auto height/width modes get handled here + menubox_moveresize(mb, mb->x, mb->y, mb->w, mb->h); + + if (MB_SELECTABLE) + { + mb->xim = XOpenIM(display, NULL, NULL, NULL); + mb->xic = XCreateIC(mb->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, mb->window, XNFocusWindow, mb->window, NULL); + } + + return mb; +} + +void menubox_moveresize(Menubox *mb, short x, short y, short w, short h) +{ + int ow = w; + + if (mb->flags & MB_AUTOHEIGHT) + h = mb->box_count * mb->line_height + (mb->padding*2); + + if (mb->flags & MB_AUTOWIDTH) + { + for (uint i = 0; i < mb->box_count; i++) + w = MAX(mb->boxes[i]->extents.width, w); + w += (mb->padding*2); + } + + if (mb->flags & MB_MINWIDTH) + { + w = MAX(ow, w); + } + + if (x != mb->x || y != mb->y || w != mb->w || h != mb->h) + { + mb->x = x; mb->y = y; mb->w = MAX(1, w); mb->h = MAX(1, h); + XMoveResizeWindow(display, mb->window, mb->x, mb->y, mb->w, mb->h); + } +} + +void menubox_add(Menubox *mb, int id, char *text) +{ + uint slot = mb->box_count++; + + mb->boxes = (Textbox**) realloc(mb->boxes, sizeof(Textbox) * mb->box_count); + mb->ids = (int*) realloc(mb->ids, sizeof(int) * mb->box_count); + + mb->boxes[slot] = textbox_create(mb->window, TB_AUTOHEIGHT|TB_AUTOWIDTH, mb->padding, mb->line_height*slot+mb->padding, mb->w-(mb->padding*2), 0, mb->font, mb->fg_blur, mb->bg_blur, text, NULL); + mb->ids[slot] = id; + + if (mb->flags & (MB_AUTOWIDTH|MB_AUTOHEIGHT)) + menubox_moveresize(mb, mb->x, mb->y, mb->w, mb->h); +} + +int menubox_get_id(Menubox *mb) +{ + return mb->ids[mb->box_active]; +} + +void menubox_grab(Menubox *mb) +{ + for (uint i = 0; i < 1000; i++) + { + if (XGrabKeyboard(display, mb->window, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) + break; + usleep(1000); + } +} + +void menubox_draw(Menubox *mb) +{ + for (uint i = 0; i < mb->box_count; i++) + { + textbox_font(mb->boxes[i], mb->font, mb->box_active == i ? mb->fg_focus: mb->fg_blur, mb->box_active == i ? mb->bg_focus: mb->bg_blur); + textbox_draw(mb->boxes[i]); + } +} + +void menubox_show(Menubox *mb) +{ + for (uint i = 0; i < mb->box_count; i++) + textbox_show(mb->boxes[i]); + XMapWindow(display, mb->window); +} + +void menubox_hide(Menubox *mb) +{ + XUnmapWindow(display, mb->window); +} + +void menubox_free(Menubox *mb) +{ + for (uint i = 0; i < mb->box_count; i++) + textbox_free(mb->boxes[i]); + free(mb->boxes); + free(mb->ids); + + if (mb->flags & MB_SELECTABLE) + { + XDestroyIC(mb->xic); + XCloseIM(mb->xim); + } + + XDestroyWindow(display, mb->window); + free(mb); +} + +void menubox_key_up(Menubox *mb) +{ + if (mb->box_active > 0) mb->box_active--; +} + +void menubox_key_down(Menubox *mb) +{ + if (mb->box_active < mb->box_count-1) mb->box_active++; +} + +void menubox_key_home(Menubox *mb) +{ + mb->box_active = 0; +} + +void menubox_key_end(Menubox *mb) +{ + mb->box_active = mb->box_count-1; +} + +// handle a keypress in edit mode +// 0 = unhandled +// 1 = handled +// -1 = handled and return pressed +// -2 = handled and escape pressed +int menubox_keypress(Menubox *mb, XEvent *ev) +{ + KeySym key; Status stat; char pad[32]; + + int len = XmbLookupString(mb->xic, &ev->xkey, pad, sizeof(pad), &key, &stat); + pad[len] = 0; + + switch (key) + { + case XK_Up : menubox_key_up(mb); return 1; + case XK_Down : menubox_key_down(mb); return 1; + case XK_Home : menubox_key_home(mb); return 1; + case XK_End : menubox_key_end(mb); return 1; + case XK_Escape : return -2; + case XK_Return : return -1; + } + return 0; +} \ No newline at end of file diff --git a/setup.c b/setup.c index 4e635f1..851e2b8 100644 --- a/setup.c +++ b/setup.c @@ -32,6 +32,7 @@ typedef struct { void setup() { int i, j; Client *c; Monitor *m; + memset(&monitors, 0, sizeof(monitors)); int screen_w = WidthOfScreen(DefaultScreenOfDisplay(display)); int screen_h = HeightOfScreen(DefaultScreenOfDisplay(display)); diff --git a/spot.c b/spot.c index c083977..1513ef6 100644 --- a/spot.c +++ b/spot.c @@ -29,7 +29,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. void spot_update_bar(int spot, int mon) { int i, n = 0, len = 0; Client *o, *c = NULL; - char title[SPOT_BUFF]; *title = 0; + char *name = NULL, title[SPOT_BUFF]; *title = 0; Monitor *m = &monitors[mon]; if (spot == SPOT1) @@ -44,11 +44,7 @@ void spot_update_bar(int spot, int mon) for_windows(i, o) if (o->manage && o->spot == spot && o->monitor == mon) { if (!c) c = o; - char *name = NULL, *tmp = NULL; - if (!(name = window_get_text_prop(o->window, atoms[_NET_WM_NAME]))) - if (XFetchName(display, o->window, &tmp)) - name = strdup(tmp); - if (name) + if ((name = window_get_name(o->window)) && name) { if (settings.title_ellipsis > 0 && strlen(name) > settings.title_ellipsis) { @@ -58,7 +54,6 @@ void spot_update_bar(int spot, int mon) len += snprintf(title+len, MAX(0, SPOT_BUFF-len), " [%d] %s ", n++, name); free(name); } - if (tmp) XFree(tmp); } if (settings.title) { diff --git a/window.c b/window.c index b676fcd..ede5920 100644 --- a/window.c +++ b/window.c @@ -65,6 +65,16 @@ char* window_get_text_prop(Window w, Atom atom) return res; } +char* window_get_name(Window w) +{ + char *name = NULL, *tmp = NULL; + if (!(name = window_get_text_prop(w, atoms[_NET_WM_NAME]))) + if (XFetchName(display, w, &tmp)) + name = strdup(tmp); + if (tmp) XFree(tmp); + return name; +} + Atom wgp_type; int wgp_items; #define GETPROP_ATOM(w, a, l, c) (window_get_prop((w), (a), &wgp_type, &wgp_items, (l), (c)*sizeof(Atom)) && wgp_type == XA_ATOM ? wgp_items:0) diff --git a/xoat.c b/xoat.c index e797823..cd3fc78 100644 --- a/xoat.c +++ b/xoat.c @@ -54,6 +54,7 @@ Display *display; #include "atom.c" #include "textbox.c" +#include "menubox.c" #include "regex.c" #define STACK 64 @@ -72,12 +73,23 @@ typedef struct _Monitor { Textbox *bars[SPOT3+1]; } Monitor; +typedef struct _MenuMap { + Window window; + char *name; +} MenuMap; + +typedef struct _Menu { + short spot, monitor; + MenuMap items[100]; + Menubox *mb; +} Menu; + typedef struct _Client { Window window; XWindowAttributes attr; Window transient, leader; Atom type, states[ATOMLIST+1]; - short monitor, visible, manage, input, urgent, full, ours, maxv, maxh; + short monitor, visible, manage, input, urgent, full, ours, maxv, maxh, bar, menu; unsigned long spot, max; char *class; } Client; @@ -185,6 +197,7 @@ Window root, ewmh, current = None; Stack windows; static int (*xerror)(Display *, XErrorEvent *); Settings settings; +Menu *menu, spot_menu; void catch_exit(int sig) { @@ -203,6 +216,7 @@ void exec_cmd(char *cmd) #include "window.c" #include "ewmh.c" #include "client.c" +#include "menu.c" #include "spot.c" #include "event.c" #include "action.c" @@ -247,6 +261,7 @@ int main(int argc, char *argv[]) self = argv[0]; root = DefaultRootWindow(display); xerror = XSetErrorHandler(oops); + menu = &spot_menu; for (i = 0; i < ATOMS; i++) atoms[i] = XInternAtom(display, atom_names[i], False);