blob: 723b6ebdfeb29fe2ed1572a3f3ef2e3f45b3b67f [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "window_manager/key_bindings.h"
#include <utility>
#include <vector>
extern "C" {
#include <X11/X.h>
#include <X11/Xutil.h>
}
#include <gflags/gflags.h>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "window_manager/util.h"
#include "window_manager/x11/x_connection.h"
using std::make_pair;
using std::map;
using std::pair;
using std::set;
using std::string;
using std::vector;
using window_manager::util::FindWithDefault;
namespace window_manager {
const uint32_t KeyBindings::kShiftMask = ShiftMask;
const uint32_t KeyBindings::kCapsLockMask = LockMask;
const uint32_t KeyBindings::kControlMask = ControlMask;
const uint32_t KeyBindings::kAltMask = Mod1Mask;
const uint32_t KeyBindings::kNumLockMask = Mod2Mask;
KeyBindings::KeyCombo::KeyCombo(KeySym key_param, uint32_t modifiers_param) {
KeySym upper_keysym = None, lower_keysym = None;
XConvertCase(key_param, &lower_keysym, &upper_keysym);
keysym = lower_keysym;
modifiers = (modifiers_param & (~kCapsLockMask & ~kNumLockMask));
}
bool KeyBindings::KeyCombo::operator<(const KeyCombo& o) const {
return (keysym < o.keysym) ||
((keysym == o.keysym) && (modifiers < o.modifiers));
}
struct Action {
Action(Closure* begin_closure_param,
Closure* repeat_closure_param,
Closure* end_closure_param)
: running(false),
begin_closure(begin_closure_param),
repeat_closure(repeat_closure_param),
end_closure(end_closure_param) {}
~Action() {
CHECK(bindings.empty());
}
// Is this action currently "running"? For certain key combinations, the
// X server will keep sending key presses while the key is held down. For
// any such sequence, the action is "running" after the first combo press
// until a combo release is seen.
bool running;
// Closure to run when the action begins (i.e. key combo press)
scoped_ptr<Closure> begin_closure;
// Closure to run on action repeat while running (i.e. key combo repeat)
scoped_ptr<Closure> repeat_closure;
// Closure to run when the action ends (i.e. key combo release)
scoped_ptr<Closure> end_closure;
// The set of key combinations currently bound to this action.
set<KeyBindings::KeyCombo> bindings;
DISALLOW_COPY_AND_ASSIGN(Action);
};
KeyBindings::KeyBindings(XConnection* xconn)
: xconn_(xconn),
current_event_time_(0),
current_key_combo_(0, 0) {
CHECK(xconn_);
if (!xconn_->SetDetectableKeyboardAutoRepeat(true)) {
LOG(WARNING) << "Unable to enable detectable keyboard autorepeat";
}
}
KeyBindings::~KeyBindings() {
while (!actions_.empty()) {
RemoveAction(actions_.begin()->first);
}
// Removing all actions should have also removed all bindings.
CHECK(bindings_.size() == 0);
}
bool KeyBindings::AddAction(const string& action_name,
Closure* begin_closure,
Closure* repeat_closure,
Closure* end_closure) {
CHECK(!action_name.empty());
if (actions_.find(action_name) != actions_.end()) {
LOG(WARNING) << "Attempting to add action that already exists: "
<< action_name;
return false;
}
Action* const action = new Action(begin_closure, repeat_closure, end_closure);
CHECK(actions_.insert(make_pair(action_name, action)).second);
return true;
}
bool KeyBindings::RemoveAction(const string& action_name) {
ActionMap::iterator iter = actions_.find(action_name);
if (iter == actions_.end()) {
LOG(WARNING) << "Attempting to remove non-existant action: " << action_name;
return false;
}
Action* const action = iter->second;
while (!action->bindings.empty()) {
CHECK(RemoveBinding(*(action->bindings.begin())));
}
delete action;
actions_.erase(iter);
return true;
}
bool KeyBindings::AddBinding(const KeyCombo& combo, const string& action_name) {
if (bindings_.find(combo) != bindings_.end()) {
LOG(WARNING) << "Attempt to overwrite existing key binding for action: "
<< action_name;
return false;
}
ActionMap::iterator iter = actions_.find(action_name);
if (iter == actions_.end()) {
LOG(WARNING) << "Attempt to add key binding for missing action: "
<< action_name;
return false;
}
Action* const action = iter->second;
CHECK(action->bindings.insert(combo).second);
CHECK(bindings_.insert(make_pair(combo, action_name)).second);
action_names_by_keysym_[combo.keysym][action_name]++;
KeyCode keycode = FindWithDefault(
keysyms_to_grabbed_keycodes_, combo.keysym, static_cast<KeyCode>(0));
if (keycode == 0) {
keycode = xconn_->GetKeyCodeFromKeySym(combo.keysym);
if (keycode != 0)
keysyms_to_grabbed_keycodes_[combo.keysym] = keycode;
}
if (keycode == 0) {
// We'll try again if the keymap changes.
LOG(WARNING) << "Unable to look up keycode for keysym " << combo.keysym
<< "; not grabbing key";
} else {
GrabKey(keycode, combo.modifiers);
}
return true;
}
bool KeyBindings::RemoveBinding(const KeyCombo& combo) {
BindingsMap::iterator bindings_iter = bindings_.find(combo);
if (bindings_iter == bindings_.end())
return false;
ActionMap::iterator action_iter = actions_.find(bindings_iter->second);
CHECK(action_iter != actions_.end());
Action* action = action_iter->second;
CHECK(action->bindings.erase(combo) == 1);
// Decrement the count of bindings for this action in the
// keysym-to-action map, and remove the entry if it was the only one.
KeySymMap::iterator keysym_iter = action_names_by_keysym_.find(combo.keysym);
CHECK(keysym_iter != action_names_by_keysym_.end());
map<string, int>::iterator count_iter =
keysym_iter->second.find(bindings_iter->second);
CHECK(count_iter != keysym_iter->second.end());
count_iter->second--;
DCHECK_GE(count_iter->second, 0);
if (count_iter->second == 0) {
keysym_iter->second.erase(count_iter);
if (keysym_iter->second.empty())
action_names_by_keysym_.erase(keysym_iter);
}
bindings_.erase(bindings_iter);
// If this action triggered its own binding's removal we won't know what
// to do with the corresponding release, so go ahead and mark the action
// as not running here.
action->running = false;
KeyCode keycode = FindWithDefault(
keysyms_to_grabbed_keycodes_, combo.keysym, static_cast<KeyCode>(0));
if (keycode != 0)
UngrabKey(keycode, combo.modifiers);
return true;
}
void KeyBindings::RefreshKeyMappings() {
map<KeySym, KeyCode> new_keysyms_to_grabbed_keycodes_;
vector<pair<KeyCode, uint32_t> > grabs_to_remove;
vector<pair<KeyCode, uint32_t> > grabs_to_add;
// Go through all of our combos, looking up the old keycodes and the new
// ones and keeping track of things that've changed.
for (BindingsMap::const_iterator it = bindings_.begin();
it != bindings_.end(); ++it) {
const KeyCombo& combo = it->first;
KeyCode old_keycode = FindWithDefault(
keysyms_to_grabbed_keycodes_, combo.keysym, static_cast<KeyCode>(0));
KeyCode new_keycode = FindWithDefault(new_keysyms_to_grabbed_keycodes_,
combo.keysym,
static_cast<KeyCode>(0));
if (new_keycode == 0) {
new_keycode = xconn_->GetKeyCodeFromKeySym(combo.keysym);
if (new_keycode != 0)
new_keysyms_to_grabbed_keycodes_[combo.keysym] = new_keycode;
}
if (new_keycode != old_keycode) {
if (old_keycode != 0)
grabs_to_remove.push_back(make_pair(old_keycode, combo.modifiers));
if (new_keycode != 0) {
grabs_to_add.push_back(make_pair(new_keycode, combo.modifiers));
} else {
LOG(WARNING) << "Unable to look up new keycode for keysym "
<< combo.keysym << "; not grabbing key";
}
}
}
// Now actually ungrab and regrab things as needed (this is done in a
// separate step in case there's overlap between the old and new mappings).
for (vector<pair<KeyCode, uint32_t> >::const_iterator it =
grabs_to_remove.begin(); it != grabs_to_remove.end(); ++it) {
UngrabKey(it->first, it->second);
}
for (vector<pair<KeyCode, uint32_t> >::const_iterator it =
grabs_to_add.begin(); it != grabs_to_add.end(); ++it) {
GrabKey(it->first, it->second);
}
keysyms_to_grabbed_keycodes_.swap(new_keysyms_to_grabbed_keycodes_);
}
bool KeyBindings::HandleKeyPress(KeyCode keycode,
uint32_t modifiers,
XTime event_time) {
const KeySym keysym = xconn_->GetKeySymFromKeyCode(keycode);
AutoReset<XTime> time_reset(&current_event_time_, event_time);
KeyCombo combo(keysym, modifiers);
BindingsMap::const_iterator bindings_iter = bindings_.find(combo);
if (bindings_iter == bindings_.end())
return false;
AutoReset<KeyCombo> combo_reset(&current_key_combo_, combo);
ActionMap::iterator action_iter = actions_.find(bindings_iter->second);
CHECK(action_iter != actions_.end());
Action* const action = action_iter->second;
if (action->running) {
if (action->repeat_closure.get()) {
action->repeat_closure->Run();
return true;
}
} else {
action->running = true;
if (action->begin_closure.get()) {
action->begin_closure->Run();
return true;
}
}
return false;
}
bool KeyBindings::HandleKeyRelease(KeyCode keycode,
uint32_t modifiers,
XTime event_time) {
const KeySym keysym = xconn_->GetKeySymFromKeyCode(keycode);
AutoReset<XTime> time_reset(&current_event_time_, event_time);
KeyCombo combo(keysym, modifiers);
// It's possible that a combo's modifier key(s) will get released before
// its non-modifier key: for an Alt+Tab combo, imagine seeing Alt press,
// Tab press, Alt release, and then Tab release. In this case, kAltMask
// won't be present in the Tab release event's modifier bitmap. We still
// want to run the end closure for the in-progress action when we receive
// the Tab release, so we check all of the non-modifier key's actions
// here to see if any of them are active.
KeySymMap::const_iterator keysym_iter =
action_names_by_keysym_.find(combo.keysym);
if (keysym_iter == action_names_by_keysym_.end())
return false;
AutoReset<KeyCombo> combo_reset(&current_key_combo_, combo);
bool ran_end_closure = false;
for (map<string, int>::const_iterator action_name_iter =
keysym_iter->second.begin();
action_name_iter != keysym_iter->second.end(); ++action_name_iter) {
ActionMap::iterator action_iter = actions_.find(action_name_iter->first);
CHECK(action_iter != actions_.end());
Action* const action = action_iter->second;
if (action->running) {
action->running = false;
if (action->end_closure.get()) {
action->end_closure->Run();
ran_end_closure = true;
}
}
}
return ran_end_closure;
}
void KeyBindings::GrabKey(KeyCode keycode, uint32_t modifiers) {
xconn_->GrabKey(keycode, modifiers);
xconn_->GrabKey(keycode, modifiers | kCapsLockMask);
xconn_->GrabKey(keycode, modifiers | kNumLockMask);
xconn_->GrabKey(keycode, modifiers | kCapsLockMask | kNumLockMask);
}
void KeyBindings::UngrabKey(KeyCode keycode, uint32_t modifiers) {
xconn_->UngrabKey(keycode, modifiers);
xconn_->UngrabKey(keycode, modifiers | kCapsLockMask);
xconn_->UngrabKey(keycode, modifiers | kNumLockMask);
xconn_->UngrabKey(keycode, modifiers | kCapsLockMask | kNumLockMask);
}
KeyBindingsActionRegistrar::~KeyBindingsActionRegistrar() {
for (set<string>::const_iterator it = action_names_.begin();
it != action_names_.end(); ++it) {
bindings_->RemoveAction(*it);
}
}
bool KeyBindingsActionRegistrar::AddAction(const std::string& action_name,
Closure* begin_closure,
Closure* repeat_closure,
Closure* end_closure) {
bool result = bindings_->AddAction(action_name,
begin_closure,
repeat_closure,
end_closure);
if (result) {
CHECK(action_names_.insert(action_name).second)
<< "Action " << action_name << " has already been registered";
}
return result;
}
KeyBindingsGroup::KeyBindingsGroup(KeyBindings* bindings)
: bindings_(bindings),
enabled_(true) {
DCHECK(bindings);
}
KeyBindingsGroup::~KeyBindingsGroup() {
SetEnabled(false);
}
void KeyBindingsGroup::AddBinding(const KeyBindings::KeyCombo& combo,
const string& action_name) {
combos_to_action_names_.insert(make_pair(combo, action_name));
if (enabled_)
bindings_->AddBinding(combo, action_name);
}
void KeyBindingsGroup::SetEnabled(bool enabled) {
if (enabled_ == enabled)
return;
enabled_ = enabled;
for (map<KeyBindings::KeyCombo, string>::const_iterator it =
combos_to_action_names_.begin();
it != combos_to_action_names_.end(); ++it) {
if (enabled)
bindings_->AddBinding(it->first, it->second);
else
bindings_->RemoveBinding(it->first);
}
}
} // namespace window_manager