all repos — openbox @ 5d5be2ba2a6e0b3886e0076475ed9d7a2d4ac9ab

openbox fork - make it a bit more like ryudo

add keyboard shortcuts to the menus. you can specify the shortcut key with & even in root menu and stuff
Dana Jansens danakj@orodu.net
commit

5d5be2ba2a6e0b3886e0076475ed9d7a2d4ac9ab

parent

138d3e38d88dbcb1426bd1eb0cd8c43dd01777ad

M openbox/client_menu.copenbox/client_menu.c

@@ -70,16 +70,14 @@ e = menu_find_entry_id(menu, CLIENT_ICONIFY);

e->data.normal.enabled = frame->client->functions & OB_CLIENT_FUNC_ICONIFY; e = menu_find_entry_id(menu, CLIENT_MAXIMIZE); - g_free(e->data.normal.label); - e->data.normal.label = - g_strdup(frame->client->max_vert || frame->client->max_horz ? - _("Restore") : _("Maximize")); + menu_entry_set_label(e, + (frame->client->max_vert || frame->client->max_horz ? + _("Restor&e") : _("Maximiz&e"))); e->data.normal.enabled =frame->client->functions & OB_CLIENT_FUNC_MAXIMIZE; e = menu_find_entry_id(menu, CLIENT_SHADE); - g_free(e->data.normal.label); - e->data.normal.label = g_strdup(frame->client->shaded ? - _("Roll down") : _("Roll up")); + menu_entry_set_label(e, (frame->client->shaded ? + _("&Roll down") : _("&Roll up"))); e->data.normal.enabled = frame->client->functions & OB_CLIENT_FUNC_SHADE; e = menu_find_entry_id(menu, CLIENT_MOVE);

@@ -165,29 +163,32 @@ GSList *acts;

ObMenu *menu; ObMenuEntry *e; - menu = menu_new(LAYER_MENU_NAME, _("Layer"), NULL); + menu = menu_new(LAYER_MENU_NAME, _("&Layer"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, layer_update); acts = g_slist_prepend(NULL, action_from_string ("SendToTopLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_TOP, _("Always on top"), acts); + menu_add_normal(menu, LAYER_TOP, _("Always on &top"), acts); acts = g_slist_prepend(NULL, action_from_string ("SendToNormalLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_NORMAL, _("Normal"), acts); + menu_add_normal(menu, LAYER_NORMAL, _("&Normal"), acts); acts = g_slist_prepend(NULL, action_from_string ("SendToBottomLayer", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, LAYER_BOTTOM, _("Always on bottom"),acts); + menu_add_normal(menu, LAYER_BOTTOM, _("Always on &bottom"),acts); - menu = menu_new(SEND_TO_MENU_NAME, _("Send to desktop"), NULL); + menu = menu_new(SEND_TO_MENU_NAME, _("&Send to desktop"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, send_to_update); menu = menu_new(CLIENT_MENU_NAME, _("Client menu"), NULL); + menu_show_all_shortcuts(menu, TRUE); menu_set_update_func(menu, client_update); menu_add_submenu(menu, CLIENT_SEND_TO, SEND_TO_MENU_NAME);

@@ -196,7 +197,7 @@ menu_add_submenu(menu, CLIENT_LAYER, LAYER_MENU_NAME);

acts = g_slist_prepend(NULL, action_from_string ("Iconify", OB_USER_ACTION_MENU_SELECTION)); - e = menu_add_normal(menu, CLIENT_ICONIFY, _("Iconify"), acts); + e = menu_add_normal(menu, CLIENT_ICONIFY, _("Ico&nify"), acts); e->data.normal.mask = ob_rr_theme->iconify_mask; e->data.normal.mask_normal_color = ob_rr_theme->menu_color; e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;

@@ -213,11 +214,11 @@ e->data.normal.mask_selected_color = ob_rr_theme->menu_selected_color;

acts = g_slist_prepend(NULL, action_from_string ("Raise", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_RAISE, _("Raise to top"), acts); + menu_add_normal(menu, CLIENT_RAISE, _("Raise to &top"), acts); acts = g_slist_prepend(NULL, action_from_string ("Lower", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_LOWER, _("Lower to bottom"),acts); + menu_add_normal(menu, CLIENT_LOWER, _("Lower to &bottom"),acts); acts = g_slist_prepend(NULL, action_from_string ("ToggleShade", OB_USER_ACTION_MENU_SELECTION));

@@ -230,23 +231,23 @@

acts = g_slist_prepend(NULL, action_from_string ("ToggleDecorations", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_DECORATE, _("Decorate"), acts); + menu_add_normal(menu, CLIENT_DECORATE, _("&Decorate"), acts); menu_add_separator(menu, -1, NULL); acts = g_slist_prepend(NULL, action_from_string ("Move", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_MOVE, _("Move"), acts); + menu_add_normal(menu, CLIENT_MOVE, _("&Move"), acts); acts = g_slist_prepend(NULL, action_from_string ("Resize", OB_USER_ACTION_MENU_SELECTION)); - menu_add_normal(menu, CLIENT_RESIZE, _("Resize"), acts); + menu_add_normal(menu, CLIENT_RESIZE, _("&Resize"), acts); menu_add_separator(menu, -1, NULL); acts = g_slist_prepend(NULL, action_from_string ("Close", OB_USER_ACTION_MENU_SELECTION)); - e = menu_add_normal(menu, CLIENT_CLOSE, _("Close"), acts); + e = menu_add_normal(menu, CLIENT_CLOSE, _("&Close"), acts); e->data.normal.mask = ob_rr_theme->close_mask; e->data.normal.mask_normal_color = ob_rr_theme->menu_color; e->data.normal.mask_disabled_color = ob_rr_theme->menu_disabled_color;
M openbox/event.copenbox/event.c

@@ -39,6 +39,7 @@ #include "moveresize.h"

#include "group.h" #include "stacking.h" #include "extensions.h" +#include "translate.h" #include <X11/Xlib.h> #include <X11/keysym.h>

@@ -72,6 +73,7 @@ } ObFocusDelayData;

static void event_process(const XEvent *e, gpointer data); static void event_handle_root(XEvent *e); +static void event_handle_menu_shortcut(XEvent *e); static void event_handle_menu(XEvent *e); static void event_handle_dock(ObDock *s, XEvent *e); static void event_handle_dockapp(ObDockApp *app, XEvent *e);

@@ -1262,7 +1264,7 @@ break;

} } -ObMenuFrame* find_active_menu() +static ObMenuFrame* find_active_menu() { GList *it; ObMenuFrame *ret = NULL;

@@ -1276,7 +1278,7 @@ }

return ret; } -ObMenuFrame* find_active_or_last_menu() +static ObMenuFrame* find_active_or_last_menu() { ObMenuFrame *ret = NULL;

@@ -1286,6 +1288,77 @@ ret = menu_frame_visible->data;

return ret; } +static void event_handle_menu_shortcut(XEvent *ev) +{ + gunichar unikey = 0; + ObMenuFrame *frame; + GList *start; + GList *it; + ObMenuEntryFrame *found = NULL; + guint num_found = 0; + + { + const char *key; + if ((key = translate_keycode(ev->xkey.keycode)) == NULL) + return; + unikey = g_utf8_get_char_validated(key, -1); + if (unikey == (gunichar)-1 || unikey == (gunichar)-2 || unikey == 0) + return; + } + + if ((frame = find_active_or_last_menu()) == NULL) + return; + + + if (!frame->entries) + return; /* nothing in the menu anyways */ + + /* start after the selected one */ + start = frame->entries; + if (frame->selected) { + for (it = start; frame->selected != it->data; it = g_list_next(it)) + g_assert(it != NULL); /* nothing was selected? */ + /* next with wraparound */ + start = g_list_next(it); + if (start == NULL) start = frame->entries; + } + + it = start; + do { + ObMenuEntryFrame *e = it->data; + gunichar entrykey = 0; + + if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) + entrykey = e->entry->data.normal.shortcut; + else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) + entrykey = e->entry->data.submenu.submenu->shortcut; + + if (unikey == entrykey) { + if (found == NULL) found = e; + ++num_found; + } + + /* next with wraparound */ + it = g_list_next(it); + if (it == NULL) it = frame->entries; + } while (it != start); + + if (found) { + if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL && + num_found == 1) + { + menu_frame_select(frame, found, TRUE); + usleep(50000); + menu_entry_frame_execute(found, ev->xkey.state, + ev->xkey.time); + } else { + menu_frame_select(frame, found, TRUE); + if (num_found == 1) + menu_frame_select_next(frame->child); + } + } +} + static void event_handle_menu(XEvent *ev) { ObMenuFrame *f;

@@ -1307,7 +1380,7 @@ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {

if (e->ignore_enters) --e->ignore_enters; else - menu_frame_select(e->frame, e); + menu_frame_select(e->frame, e, FALSE); } break; case LeaveNotify:

@@ -1315,28 +1388,35 @@ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&

(f = find_active_menu()) && f->selected == e && e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU) { - menu_frame_select(e->frame, NULL); + menu_frame_select(e->frame, NULL, FALSE); } case MotionNotify: if ((e = menu_entry_frame_under(ev->xmotion.x_root, ev->xmotion.y_root))) - menu_frame_select(e->frame, e); + menu_frame_select(e->frame, e, FALSE); break; case KeyPress: if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) - menu_frame_hide_all(); + if ((f = find_active_or_last_menu()) && f->parent) + menu_frame_select(f, NULL, TRUE); + else + menu_frame_hide_all(); else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) { ObMenuFrame *f; - if ((f = find_active_menu())) - menu_entry_frame_execute(f->selected, ev->xkey.state, - ev->xkey.time); + if ((f = find_active_menu())) { + if (f->child) + menu_frame_select_next(f->child); + else + menu_entry_frame_execute(f->selected, ev->xkey.state, + ev->xkey.time); + } } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) { ObMenuFrame *f; - if ((f = find_active_or_last_menu()) && f->parent) - menu_frame_select(f, NULL); + if ((f = find_active_or_last_menu())) + menu_frame_select(f, NULL, TRUE); } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) { ObMenuFrame *f; - if ((f = find_active_or_last_menu()) && f->child) + if ((f = find_active_menu()) && f->child) menu_frame_select_next(f->child); } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) { ObMenuFrame *f;

@@ -1346,7 +1426,8 @@ } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {

ObMenuFrame *f; if ((f = find_active_or_last_menu())) menu_frame_select_next(f); - } + } else + event_handle_menu_shortcut(ev); break; } }
M openbox/menu.copenbox/menu.c

@@ -54,6 +54,9 @@ xmlDocPtr doc, xmlNodePtr node,

gpointer data); static void parse_menu(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node, gpointer data); +static gunichar parse_shortcut(const gchar *label, gchar **strippedlabel, + guint *position); + static void client_dest(ObClient *client, gpointer data) {

@@ -178,6 +181,56 @@ name);

return self; } +#define VALID_SHORTCUT(c) (((c) >= '0' && (c) <= '9') || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z')) + +static gunichar parse_shortcut(const gchar *label, gchar **strippedlabel, + guint *position) +{ + gunichar shortcut = 0; + + *position = 0; + + g_assert(strippedlabel != NULL); + + if (label == NULL) { + *strippedlabel = NULL; + } else { + gchar *i; + + *strippedlabel = g_strdup(label); + + i = strchr(*strippedlabel, '&'); + if (i != NULL) { + /* there is an ampersand in the string */ + + /* you have to use a printable ascii character for shortcuts + don't allow space either, so you can have like "a & b" + */ + if (VALID_SHORTCUT(*(i+1))) { + shortcut = g_unichar_tolower(g_utf8_get_char(i+1)); + *position = i - *strippedlabel; + + /* remove the & from the string */ + for (; *i != '\0'; ++i) + *i = *(i+1); + } + } else { + /* there is no ampersand, so find the first valid character to use + instead */ + + for (i = *strippedlabel; *i != '\0'; ++i) + if (VALID_SHORTCUT(*i)) { + *position = i - *strippedlabel; + shortcut = g_unichar_tolower(g_utf8_get_char(i)); + break; + } + } + } + return shortcut; +} + static void parse_menu_item(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node, gpointer data) {

@@ -262,9 +315,11 @@ ObMenu *self;

self = g_new0(ObMenu, 1); self->name = g_strdup(name); - self->title = g_strdup(title); self->data = data; + self->shortcut = parse_shortcut(title, &self->title, + &self->shortcut_position); + g_hash_table_replace(menu_hash, self->name, self); return self;

@@ -325,7 +380,7 @@ else if (frame->entries) {

ObMenuEntryFrame *e = frame->entries->data; if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL && e->entry->data.normal.enabled) - menu_frame_select(frame, e); + menu_frame_select(frame, e, FALSE); } }

@@ -409,9 +464,10 @@ {

ObMenuEntry *e; e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL, id); - e->data.normal.label = g_strdup(label); e->data.normal.actions = actions; + menu_entry_set_label(e, label); + self->entries = g_list_append(self->entries, e); return e; }

@@ -432,7 +488,8 @@ {

ObMenuEntry *e; e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR, id); - e->data.separator.label = g_strdup(label); + + menu_entry_set_label(e, label); self->entries = g_list_append(self->entries, e); return e;

@@ -480,3 +537,26 @@ if (e->type == OB_MENU_ENTRY_TYPE_SUBMENU)

e->data.submenu.submenu = menu_from_name(e->data.submenu.name); } } + +void menu_entry_set_label(ObMenuEntry *self, const gchar *label) +{ + switch (self->type) { + case OB_MENU_ENTRY_TYPE_SEPARATOR: + g_free(self->data.separator.label); + self->data.separator.label = g_strdup(label); + break; + case OB_MENU_ENTRY_TYPE_NORMAL: + g_free(self->data.normal.label); + self->data.normal.shortcut = + parse_shortcut(label, &self->data.normal.label, + &self->data.normal.shortcut_position); + break; + default: + g_assert_not_reached(); + } +} + +void menu_show_all_shortcuts(ObMenu *self, gboolean show) +{ + self->show_all_shortcuts = show; +}
M openbox/menu.hopenbox/menu.h

@@ -48,6 +48,15 @@ /* Name of the menu. Used in the showmenu action. */

gchar *name; /* Displayed title */ gchar *title; + /*! The shortcut key that would be used to activate this menu if it was + displayed as a submenu */ + gunichar shortcut; + /*! The shortcut's position in the string */ + guint shortcut_position; + + /*! If the shortcut key should be shown in menu entries even when it + is the first character in the string */ + gboolean show_all_shortcuts; /* Command to execute to rebuild the menu */ gchar *execute;

@@ -75,6 +84,10 @@ } ObMenuEntryType;

struct _ObNormalMenuEntry { gchar *label; + /*! The shortcut key that would be used to activate this menu entry */ + gunichar shortcut; + /*! The shortcut's position in the string */ + guint shortcut_position; /* state */ gboolean enabled;

@@ -126,6 +139,8 @@

/* Repopulate a pipe-menu by running its command */ void menu_pipe_execute(ObMenu *self); +void menu_show_all_shortcuts(ObMenu *self, gboolean show); + void menu_show(gchar *name, gint x, gint y, struct _ObClient *client); void menu_set_update_func(ObMenu *menu, ObMenuUpdateFunc func);

@@ -140,6 +155,8 @@ ObMenuEntry* menu_add_separator(ObMenu *menu, gint id, const gchar *label);

void menu_clear_entries(ObMenu *menu); void menu_entry_remove(ObMenuEntry *self); + +void menu_entry_set_label(ObMenuEntry *self, const gchar *label); ObMenuEntry* menu_find_entry_id(ObMenu *self, gint id);
M openbox/menuframe.copenbox/menuframe.c

@@ -318,6 +318,13 @@ (self == self->frame->selected ?

self->a_text_selected : self->a_text_normal)); text_a->texture[0].data.text.string = self->entry->data.normal.label; + if (self->frame->menu->show_all_shortcuts || + self->entry->data.normal.shortcut_position > 0) + { + text_a->texture[0].data.text.shortcut = + self->entry->data.normal.shortcut; + } else + text_a->texture[0].data.text.shortcut = 0; break; case OB_MENU_ENTRY_TYPE_SUBMENU: text_a = (self == self->frame->selected ?

@@ -325,6 +332,11 @@ self->a_text_selected :

self->a_text_normal); sub = self->entry->data.submenu.submenu; text_a->texture[0].data.text.string = sub ? sub->title : ""; + if (self->frame->menu->show_all_shortcuts || + sub->shortcut_position > 0) { + text_a->texture[0].data.text.shortcut = sub->shortcut; + } else + text_a->texture[0].data.text.shortcut = 0; break; case OB_MENU_ENTRY_TYPE_SEPARATOR: if (self->entry->data.separator.label != NULL)

@@ -886,7 +898,8 @@ menu_entry_frame_show_submenu((ObMenuEntryFrame*)data);

return FALSE; } -void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry) +void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry, + gboolean immediate) { ObMenuEntryFrame *old = self->selected; ObMenuFrame *oldchild = self->child;

@@ -913,7 +926,7 @@ if (self->selected) {

menu_entry_frame_render(self->selected); if (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) { - if (config_submenu_show_delay) { + if (config_submenu_show_delay && !immediate) { /* initiate a new submenu open request */ ob_main_loop_timeout_add(ob_main_loop, config_submenu_show_delay * 1000,

@@ -988,7 +1001,7 @@ break;

} } } - menu_frame_select(self, it ? it->data : NULL); + menu_frame_select(self, it ? it->data : NULL, TRUE); } void menu_frame_select_next(ObMenuFrame *self)

@@ -1014,5 +1027,5 @@ break;

} } } - menu_frame_select(self, it ? it->data : NULL); + menu_frame_select(self, it ? it->data : NULL, TRUE); }
M openbox/menuframe.hopenbox/menuframe.h

@@ -124,7 +124,8 @@

void menu_frame_hide_all(); void menu_frame_hide_all_client(struct _ObClient *client); -void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry); +void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry, + gboolean immediate); void menu_frame_select_previous(ObMenuFrame *self); void menu_frame_select_next(ObMenuFrame *self);
M openbox/translate.copenbox/translate.c

@@ -139,3 +139,13 @@ translation_fail:

g_strfreev(parsed); return ret; } + +const gchar *translate_keycode(guint keycode) +{ + KeySym sym; + const gchar *ret = NULL; + + if ((sym = XKeycodeToKeysym(ob_display, keycode, 0)) != NoSymbol) + ret = XKeysymToString(sym); + return g_locale_to_utf8(ret, -1, NULL, NULL, NULL); +}
M openbox/translate.hopenbox/translate.h

@@ -24,4 +24,6 @@

gboolean translate_button(const gchar *str, guint *state, guint *keycode); gboolean translate_key(const gchar *str, guint *state, guint *keycode); +/*! Give the string form of a keycode */ +const gchar *translate_keycode(guint keycode); #endif