openbox/openbox/keyboard.c
Dana Jansens 220e568d75 allow noninteractive directional focus.
make keybinds noninteractive when the last key in the chain has no modifiers (i think this is what the code was supposed to do but failed.)

don't reset key chains during an interactive action. reset them after an interactive action finishes (honoring chroot)
so you can <C-t><C-Up>Stuff<//> and it'll do stuff as you hit C-Up, you dont have to keep hitting C-t
2007-06-10 17:58:52 +00:00

361 lines
9.6 KiB
C

/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
keyboard.c for the Openbox window manager
Copyright (c) 2006 Mikael Magnusson
Copyright (c) 2003-2007 Dana Jansens
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
See the COPYING file for a copy of the GNU General Public License.
*/
#include "mainloop.h"
#include "focus.h"
#include "screen.h"
#include "frame.h"
#include "openbox.h"
#include "event.h"
#include "grab.h"
#include "client.h"
#include "action.h"
#include "prop.h"
#include "menuframe.h"
#include "config.h"
#include "keytree.h"
#include "keyboard.h"
#include "translate.h"
#include "moveresize.h"
#include "popup.h"
#include "gettext.h"
#include <glib.h>
typedef struct {
gboolean active;
guint state;
ObClient *client;
ObAction *action;
} ObInteractiveState;
KeyBindingTree *keyboard_firstnode = NULL;
static ObPopup *popup = NULL;
static ObInteractiveState istate;
static KeyBindingTree *curpos;
static void grab_keys(gboolean grab)
{
KeyBindingTree *p;
ungrab_all_keys(RootWindow(ob_display, ob_screen));
if (grab) {
p = curpos ? curpos->first_child : keyboard_firstnode;
while (p) {
grab_key(p->key, p->state, RootWindow(ob_display, ob_screen),
GrabModeAsync);
p = p->next_sibling;
}
if (curpos)
grab_key(config_keyboard_reset_keycode,
config_keyboard_reset_state,
RootWindow(ob_display, ob_screen), GrabModeAsync);
}
}
static gboolean chain_timeout(gpointer data)
{
keyboard_reset_chains(0);
return FALSE; /* don't repeat */
}
static void set_curpos(KeyBindingTree *newpos)
{
if (curpos == newpos) return;
grab_keys(FALSE);
curpos = newpos;
grab_keys(TRUE);
if (curpos != NULL) {
gchar *text = NULL;
GList *it;
Rect *a;
for (it = curpos->keylist; it; it = g_list_next(it)) {
gchar *oldtext = text;
if (text == NULL)
text = g_strdup(it->data);
else
text = g_strconcat(text, " - ", it->data, NULL);
g_free(oldtext);
}
a = screen_physical_area_monitor_active();
popup_position(popup, NorthWestGravity, a->x + 10, a->y + 10);
/* 1 second delay for the popup to show */
popup_delay_show(popup, G_USEC_PER_SEC, text);
g_free(text);
} else {
popup_hide(popup);
}
}
void keyboard_reset_chains(gint break_chroots)
{
KeyBindingTree *p;
for (p = curpos; p; p = p->parent) {
if (p->chroot) {
if (break_chroots == 0) break; /* stop here */
if (break_chroots > 0)
--break_chroots;
}
}
set_curpos(p);
}
void keyboard_unbind_all()
{
tree_destroy(keyboard_firstnode);
keyboard_firstnode = NULL;
}
void keyboard_chroot(GList *keylist)
{
/* try do it in the existing tree. if we can't that means it is an empty
chroot binding. so add it to the tree then. */
if (!tree_chroot(keyboard_firstnode, keylist)) {
KeyBindingTree *tree;
if (!(tree = tree_build(keylist)))
return;
tree_chroot(tree, keylist);
tree_assimilate(tree);
}
}
gboolean keyboard_bind(GList *keylist, ObAction *action)
{
KeyBindingTree *tree, *t;
gboolean conflict;
g_assert(keylist != NULL);
g_assert(action != NULL);
if (!(tree = tree_build(keylist)))
return FALSE;
if ((t = tree_find(tree, &conflict)) != NULL) {
/* already bound to something, use the existing tree */
tree_destroy(tree);
tree = NULL;
} else
t = tree;
if (conflict) {
g_message(_("Conflict with key binding in config file"));
tree_destroy(tree);
return FALSE;
}
/* find the bottom node */
for (; t->first_child; t = t->first_child);
/* when there are no modifiers in the binding, then the action cannot
be interactive */
if (!t->state && action->data.any.interactive) {
g_print("not interactive\n");
action->data.any.interactive = FALSE;
action->data.inter.final = TRUE;
}
/* set the action */
t->actions = g_slist_append(t->actions, action);
/* assimilate this built tree into the main tree. assimilation
destroys/uses the tree */
if (tree) tree_assimilate(tree);
return TRUE;
}
static void keyboard_interactive_end(guint state, gboolean cancel, Time time,
gboolean ungrab)
{
GSList *alist;
g_assert(istate.active);
/* ungrab first so they won't be NotifyWhileGrabbed */
if (ungrab)
ungrab_keyboard();
/* set this before running the actions so they know the keyboard is not
grabbed */
istate.active = FALSE;
alist = g_slist_append(NULL, istate.action);
action_run_interactive(alist, istate.client, state, time, cancel, TRUE);
g_slist_free(alist);
keyboard_reset_chains(0);
}
static void keyboard_interactive_end_client(ObClient *client, gpointer data)
{
if (istate.active && istate.client == client)
istate.client = NULL;
}
void keyboard_interactive_cancel()
{
keyboard_interactive_end(0, TRUE, event_curtime, TRUE);
}
gboolean keyboard_interactive_grab(guint state, ObClient *client,
ObAction *action)
{
g_assert(action->data.any.interactive);
if (!istate.active) {
if (!grab_keyboard())
return FALSE;
} else if (action->func != istate.action->func) {
keyboard_interactive_end(state, TRUE, action->data.any.time, FALSE);
}
istate.active = TRUE;
istate.state = state;
istate.client = client;
istate.action = action;
return TRUE;
}
gboolean keyboard_process_interactive_grab(const XEvent *e, ObClient **client)
{
gboolean handled = FALSE;
gboolean done = FALSE;
gboolean cancel = FALSE;
if (istate.active) {
if ((e->type == KeyRelease && !(istate.state & e->xkey.state))) {
done = TRUE;
handled = TRUE;
} else if (e->type == KeyPress) {
/*if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
done = TRUE;
else */if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
cancel = done = TRUE;
handled = TRUE;
}
} else if (e->type == ButtonPress) {
cancel = TRUE;
done = TRUE;
handled = FALSE;
}
if (done)
keyboard_interactive_end(e->xkey.state, cancel, e->xkey.time,TRUE);
if (handled)
*client = istate.client;
}
return handled;
}
void keyboard_event(ObClient *client, const XEvent *e)
{
KeyBindingTree *p;
g_assert(e->type == KeyPress);
if (e->xkey.keycode == config_keyboard_reset_keycode &&
e->xkey.state == config_keyboard_reset_state)
{
ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
keyboard_reset_chains(-1);
return;
}
if (curpos == NULL)
p = keyboard_firstnode;
else
p = curpos->first_child;
while (p) {
if (p->key == e->xkey.keycode &&
p->state == e->xkey.state)
{
/* if we hit a key binding, then close any open menus and run it */
if (menu_frame_visible)
menu_frame_hide_all();
if (p->first_child != NULL) { /* part of a chain */
ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
/* 3 second timeout for chains */
ob_main_loop_timeout_add(ob_main_loop, 3 * G_USEC_PER_SEC,
chain_timeout, NULL,
g_direct_equal, NULL);
set_curpos(p);
} else if (p->chroot) /* an empty chroot */
set_curpos(p);
else {
GSList *it;
gboolean inter = FALSE;
for (it = p->actions; it && !inter; it = g_slist_next(it))
if (((ObAction*)it->data)->data.any.interactive)
inter = TRUE;
if (!inter) /* don't reset if the action is interactive */
keyboard_reset_chains(0);
action_run_key(p->actions, client, e->xkey.state,
e->xkey.x_root, e->xkey.y_root,
e->xkey.time);
}
break;
}
p = p->next_sibling;
}
}
gboolean keyboard_interactively_grabbed()
{
return istate.active;
}
void keyboard_startup(gboolean reconfig)
{
grab_keys(TRUE);
popup = popup_new(FALSE);
popup_set_text_align(popup, RR_JUSTIFY_CENTER);
if (!reconfig)
client_add_destroy_notify(keyboard_interactive_end_client, NULL);
}
void keyboard_shutdown(gboolean reconfig)
{
if (!reconfig)
client_remove_destroy_notify(keyboard_interactive_end_client);
if (istate.active)
keyboard_interactive_cancel();
ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
keyboard_unbind_all();
set_curpos(NULL);
popup_free(popup);
popup = NULL;
}