blob: bc5668ff1004434e7bbe5d7521f888037511d5d8 [file] [log] [blame]
// Copyright 2016 The Chromium 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 "remoting/host/linux/x11_character_injector.h"
#include <algorithm>
#include "base/bind.h"
#include "remoting/host/linux/x11_keyboard.h"
namespace {
constexpr base::TimeDelta kMappingExpireDuration =
base::TimeDelta::FromMilliseconds(200);
} // namespace
namespace remoting {
struct X11CharacterInjector::KeyInfo {
uint32_t keycode;
base::TimeTicks expire_at;
};
struct X11CharacterInjector::MapResult {
bool success;
uint32_t keycode;
uint32_t modifiers;
// If success == false and |retry_after| is not zero, user may retry
// AddNewCharacter() after |retry_after| has elapsed.
base::TimeDelta retry_after;
};
X11CharacterInjector::X11CharacterInjector(
std::unique_ptr<X11Keyboard> keyboard)
: keyboard_(std::move(keyboard)) {
std::vector<uint32_t> keycodes = keyboard_->GetUnusedKeycodes();
for (int keycode : keycodes) {
available_keycodes_.push_back({keycode, base::TimeTicks()});
}
}
X11CharacterInjector::~X11CharacterInjector() {
DCHECK(thread_checker_.CalledOnValidThread());
// Clear all used key mappings.
for (const KeyInfo& info : available_keycodes_) {
if (!info.expire_at.is_null()) {
keyboard_->ChangeKeyMapping(info.keycode, 0);
}
}
keyboard_->Sync();
}
void X11CharacterInjector::Inject(uint32_t code_point) {
DCHECK(thread_checker_.CalledOnValidThread());
characters_queue_.push(code_point);
Schedule(base::TimeDelta());
}
void X11CharacterInjector::Schedule(base::TimeDelta delay) {
DCHECK(thread_checker_.CalledOnValidThread());
if (injection_timer_.IsRunning()) {
return;
}
injection_timer_.Start(FROM_HERE, delay, this,
&X11CharacterInjector::DoInject);
}
void X11CharacterInjector::DoInject() {
DCHECK(thread_checker_.CalledOnValidThread());
while (!characters_queue_.empty()) {
uint32_t code_point = characters_queue_.front();
MapResult result = MapCharacter(code_point);
if (!result.success) {
if (result.retry_after.is_zero()) {
characters_queue_.pop();
continue;
}
Schedule(result.retry_after);
break;
}
keyboard_->PressKey(result.keycode, result.modifiers);
characters_queue_.pop();
}
keyboard_->Flush();
}
X11CharacterInjector::MapResult X11CharacterInjector::MapCharacter(
uint32_t code_point) {
MapResult result{false, 0, 0, base::TimeDelta()};
base::TimeTicks now = base::TimeTicks::Now();
if (keyboard_->FindKeycode(code_point, &result.keycode, &result.modifiers)) {
uint32_t keycode = result.keycode;
auto position = std::find_if(
available_keycodes_.begin(), available_keycodes_.end(),
[keycode](const KeyInfo& info) { return info.keycode == keycode; });
if (position != available_keycodes_.end()) {
ResetKeyInfoExpirationTime(now, position);
}
result.success = true;
return result;
}
if (available_keycodes_.empty()) {
return result;
}
KeyInfo& info = available_keycodes_.front();
if (info.expire_at > now) {
result.retry_after = info.expire_at - now;
return result;
}
if (!keyboard_->ChangeKeyMapping(info.keycode, code_point)) {
return result;
}
result.success = true;
result.keycode = info.keycode;
// Modifiers can always be 0 since |code_point| is mapped to both upper and
// lower case.
ResetKeyInfoExpirationTime(now, available_keycodes_.begin());
keyboard_->Sync();
return result;
}
void X11CharacterInjector::ResetKeyInfoExpirationTime(
base::TimeTicks now,
std::vector<KeyInfo>::iterator position) {
KeyInfo info = *position;
info.expire_at = now + kMappingExpireDuration;
available_keycodes_.erase(position);
available_keycodes_.push_back(info);
}
} // namespace remoting