根据前面文章的分析,InputReader 创建的代码如下
// InputManager.cpp
InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
InputDispatcherPolicyInterface& dispatcherPolicy) {
mDispatcher = createInputDispatcher(dispatcherPolicy);
mProcessor = std::make_unique<InputProcessor>(*mDispatcher);
mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mProcessor);
// 创建 InputReader
mReader = createInputReader(readerPolicy, *mBlocker);
}
// InputReaderFactory.cpp
std::unique_ptr<InputReaderInterface> createInputReader(
const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener) {
// 先创建 EventHub,再创建 InputReader
return std::make_unique<InputReader>(std::make_unique<EventHub>(), policy, listener);
}
InputReader 需要从 EventHub 中获取事件,因此需要先创建 EventHub。
创建 EventHub
// EventHub.cpp
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
// mNeedToScanDevices 被初始化为 true,表示需要扫描设备
mNeedToScanDevices(true),
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend();
// 创建 epoll
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
// 创建 inotify
mINotifyFd = inotify_init1(IN_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance: %s", strerror(errno));
std::error_code errorCode;
bool isDeviceInotifyAdded = false;
// 存在 /dev/input 目录
if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
// inotify 监听 /dev/input 目录,也就是监听输入设备的增删
addDeviceInputInotify();
} else {
// ...
}
if (isV4lScanningEnabled() && !isDeviceInotifyAdded) {
addDeviceInotify();
} else {
ALOGI("Video device scanning disabled");
}
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
// epoll 监听 inotify 事件
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
int wakeFds[2];
// 创建用户唤醒的 pipe
result = pipe2(wakeFds, O_CLOEXEC);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
// 保存 pipe 的两端
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
// 设置管道两端为非阻塞状态
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
eventItem.data.fd = mWakeReadPipeFd;
// epoll 监听 pipe 的读端
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
EventHub 创建时,主要做了两件事情
- 通过 inotify ,监听 /dev/input 目录,也就是监听输入设备的增删。然后用 epoll 监听 inotify 事件。那么,当发生设备增删的时候,epoll 就会收到事件,然后通知 InputReader 处理。
- 创建一个 pipe(管道),epoll 只监听管道的读端。写端被其他模块使用,例如,当输入设备配置改变时,通过管道的写端写入数据,然后可以唤醒 InputReader 处理配置改变。
epoll, inotify, pipe,它们的作用和使用方式,请读者自行查阅 Unix/Linux 资料。
创建 InputReader
// InputReader.cpp
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
InputListenerInterface& listener)
: mContext(this),
mEventHub(eventHub),
mPolicy(policy), // 指向 NativeInputManager
// 初始化 QueuedInputListener mQueuedListener
// listener 指向 UnwantedInteractionBlocker
mQueuedListener(listener),
mGlobalMetaState(AMETA_NONE),
mLedMetaState(AMETA_NONE),
mGeneration(1),
mNextInputDeviceId(END_RESERVED_ID),
mDisableVirtualKeysTimeout(LLONG_MIN),
mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
// 刷新配置
refreshConfigurationLocked(/*changes=*/{});
// 此时还没有扫描输入设备,因此没有 global meta state
updateGlobalMetaStateLocked();
}
void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) {
// 获取配置,保存到 InputReader::mConfig
mPolicy->getReaderConfiguration(&mConfig);
// EventHub::mExcludedDevices 保存排除的输入设备
mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
using Change = InputReaderConfiguration::Change;
if (!changes.any()) return;
// ...
}
InputReader 的构造函数,做了一些变量初始化,然后最主要的是刷新配置,如下
// com_android_server_input_InputManagerService.cpp
void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getVirtualKeyQuietTimeMillis);
if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
}
outConfig->excludedDeviceNames.clear();
jobjectArray excludedDeviceNames = jobjectArray(env->CallStaticObjectMethod(
gServiceClassInfo.clazz, gServiceClassInfo.getExcludedDeviceNames));
if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
jsize length = env->GetArrayLength(excludedDeviceNames);
for (jsize i = 0; i < length; i++) {
std::string deviceName = getStringElementFromJavaArray(env, excludedDeviceNames, i);
outConfig->excludedDeviceNames.push_back(deviceName);
}
env->DeleteLocalRef(excludedDeviceNames);
}
// Associations between input ports and display ports
// The java method packs the information in the following manner:
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.
outConfig->portAssociations.clear();
jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
jsize length = env->GetArrayLength(portAssociations);
for (jsize i = 0; i < length / 2; i++) {
std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
std::string displayPortStr =
getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
uint8_t displayPort;
// Should already have been validated earlier, but do it here for safety.
bool success = ParseUint(displayPortStr, &displayPort);
if (!success) {
ALOGE("Could not parse entry in port configuration file, received: %s",
displayPortStr.c_str());
continue;
}
outConfig->portAssociations.insert({inputPort, displayPort});
}
env->DeleteLocalRef(portAssociations);
}
outConfig->uniqueIdAssociations =
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
.getInputUniqueIdAssociations,
"getInputUniqueIdAssociations");
outConfig->deviceTypeAssociations =
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
.getDeviceTypeAssociations,
"getDeviceTypeAssociations");
outConfig->keyboardLayoutAssociations = readMapFromInterleavedJavaArray<
KeyboardLayoutInfo>(gServiceClassInfo.getKeyboardLayoutAssociations,
"getKeyboardLayoutAssociations", [](auto&& layoutIdentifier) {
size_t commaPos = layoutIdentifier.find(',');
std::string languageTag = layoutIdentifier.substr(0, commaPos);
std::string layoutType = layoutIdentifier.substr(commaPos + 1);
return KeyboardLayoutInfo(std::move(languageTag),
std::move(layoutType));
});
jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getDoubleTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
jint longPressTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getLongPressTimeout);
if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);
// We must ensure that the tap-drag interval is significantly shorter than
// the long-press timeout because the tap is held down for the entire duration
// of the double-tap timeout.
jint tapDragInterval = max(min(longPressTimeout - 100,
doubleTapTimeout), hoverTapTimeout);
outConfig->pointerGestureTapDragInterval =
milliseconds_to_nanoseconds(tapDragInterval);
}
}
}
jint hoverTapSlop = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapSlop);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
{ // acquire lock
std::scoped_lock _l(mLock);
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration = mLocked.pointerAcceleration;
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->showTouches = mLocked.showTouches;
outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
outConfig->setDisplayViewports(mLocked.viewports);
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
} // release lock
}
InputReader 的配置 mConfig,大部分来自于上层,我们可以通过 dumpsys input 来观测到大部分的配置数据,如下
Input Reader State (Nums of device: 9):
...
Configuration:
ExcludedDeviceNames: [qti-haptics, qcom-hv-haptics]
VirtualKeyQuietTime: 0.0ms
PointerVelocityControlParameters: scale=1.000, lowThreshold=500.000, highThreshold=3000.000, acceleration=3.000
WheelVelocityControlParameters: scale=1.000, lowThreshold=15.000, highThreshold=50.000, acceleration=4.000
PointerGesture:
Enabled: true
QuietInterval: 100.0ms
DragMinSwitchSpeed: 50.0px/s
TapInterval: 150.0ms
TapDragInterval: 300.0ms
TapSlop: 20.0px
MultitouchSettleInterval: 100.0ms
MultitouchMinDistance: 15.0px
SwipeTransitionAngleCosine: 0.3
SwipeMaxWidthRatio: 0.2
MovementSpeedRatio: 0.8
ZoomSpeedRatio: 0.3
Viewports:
Viewport INTERNAL: displayId=0, uniqueId=local:4630946650788219010, port=130, orientation=0, logicalFrame=[0, 0, 1080, 2400], physicalFrame=[0, 0, 1260, 2800], deviceSize=[1260, 2800], isActive=[1]
InputReder 线程循环
根据前面一篇文章的分析可知,InputReader 在启动时,是创建一个线程,然后循环执行 loopOnce()
// InputReader.cpp
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
// Copy some state so that we can access it outside the lock later.
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
std::list<NotifyArgs> notifyArgs;
{ // acquire lock
std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
auto changes = mConfigurationChangesToRefresh;
if (changes.any()) { // 处理配置改变
mConfigurationChangesToRefresh.clear();
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
// 1. 从 EventHub 读取事件
std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
// 2. 处理读取到的事件
if (!events.empty()) {
notifyArgs += processEventsLocked(events.data(), events.size());
}
if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
if (debugRawEvents()) {
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
}
mNextTimeout = LLONG_MAX;
notifyArgs += timeoutExpiredLocked(now);
}
}
// 处理设备改变
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
notifyArgs.emplace_back(
NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices});
}
} // release lock
// 如果设备改变了,那么把这些数据数据发送给上层 InputManagerService
// InputManagerService 使用 mInputDevices 保存这些数据
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// Notify the policy of the start of every new stylus gesture outside the lock.
for (const auto& args : notifyArgs) {
const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
}
}
// 3. 把所有事件加入到 mQueuedListener 的队列中
notifyAll(std::move(notifyArgs));
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
// on another thread similarly waiting to acquire the InputReader lock thereby
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
// 4. 刷新 mQueuedListener,把所有事件分发给下一环
mQueuedListener.flush();
}
InputReader 在每一次线程循环中,会从 EventHub 获取事件,当然,如果输入设备并没有产生事件,那么 InputReader 线程就阻塞了。
当 InputReader 从 EventHub 获取到事件之后,就处理事件。 EventHub 的输入设备的事件,来自于驱动上报,它们称之为 raw event(元事件)。InputReader 处理 raw event 后,会把事件重新包装成 NotifyArgs,然后加入到 mQueuedListener 的队列中。
最后,mQueuedListener 刷新队列,把队列中所有事件,逐个发送给下一环。根据前面的文章分析可知,下一环是 UnwantedInteractionBlocker 。
从 Eventhub 获取事件
// EventHub.cpp
std::vector<RawEvent> EventHub::getEvents(int timeoutMillis) {
std::scoped_lock _l(mLock);
std::array<input_event, EVENT_BUFFER_SIZE> readBuffer;
// events 保存的是返回给 InputReader 的事件
std::vector<RawEvent> events;
bool awoken = false;
// 通过一个无限循环,不断获取数据,直到有数据才跳出循环
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// Reopen input devices if needed.
if (mNeedToReopenDevices) {
// ...
break; // return to the caller before we actually rescan
}
// Report any devices that had last been added/removed.
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
// ...
}
// mNeedToScanDevices 初始值为 true
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
// 扫描输入设备
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
// 处理扫描后的数据设备
while (!mOpeningDevices.empty()) {
std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
mOpeningDevices.pop_back();
ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());
const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
// 为每一个输入设备填充一个 DEVICE_ADDED 事件
events.push_back({
.when = now,
.deviceId = deviceId,
.type = DEVICE_ADDED,
});
// Try to find a matching video device by comparing device names
for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
it++) {
std::unique_ptr<TouchVideoDevice>& videoDevice = *it;
if (tryAddVideoDeviceLocked(*device, videoDevice)) {
// videoDevice was transferred to 'device'
it = mUnattachedVideoDevices.erase(it);
break;
}
}
// mDevices 以 id 为 KEY 保存输入设备数据
// mDevices : id -> Device
auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
if (!inserted) {
ALOGW("Device id %d exists, replaced.", device->id);
}
mNeedToSendFinishedDeviceScan = true;
if (events.size() == EVENT_BUFFER_SIZE) {
break;
}
}
// 设备扫描完成后,补充一个 FINISHED_DEVICE_SCAN 事件
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
events.push_back({
.when = now,
.type = FINISHED_DEVICE_SCAN,
});
if (events.size() == EVENT_BUFFER_SIZE) {
break;
}
}
// 处理从 epoll 获取的数据
bool deviceChanged = false;
while (mPendingEventIndex < mPendingEventCount) {
// 注意,mPendingEventIndex 加 1 了
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
// 数据来自于 inotify ,表明有设备增删
if (eventItem.data.fd == mINotifyFd) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
// 数组来自于管道
if (eventItem.data.fd == mWakeReadPipeFd) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
// 需要唤醒 InputReader 线程处理事件
awoken = true;
char wakeReadBuffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
Device* device = getDeviceByFdLocked(eventItem.data.fd);
if (device == nullptr) {
ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events,
eventItem.data.fd);
ALOG_ASSERT(!DEBUG);
continue;
}
if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {
// ...
continue;
}
// This must be an input event
// 处理输入设备的事件,例如 motion event,key event
if (eventItem.events & EPOLLIN) {
int32_t readSize =
read(device->fd, readBuffer.data(),
sizeof(decltype(readBuffer)::value_type) * readBuffer.size());
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
// ...
} else if (readSize < 0) {
// ...
} else if ((readSize % sizeof(struct input_event)) != 0) {
// ...
} else {
const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
// 为来自输入设备的每一个事件,填充一个数据
const size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
events.push_back({
.when = processEventTimestamp(iev),
.readTime = systemTime(SYSTEM_TIME_MONOTONIC),
.deviceId = deviceId,
.type = iev.type,
.code = iev.code,
.value = iev.value,
});
}
if (events.size() >= EVENT_BUFFER_SIZE) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
mPendingEventIndex -= 1;
break;
}
}
} else if (eventItem.events & EPOLLHUP) {
// ...
} else {
// ...
}
}
// 处理 inotify 事件
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
// 处理输入设备的增删
// 如果是增加了设备,会保存到 mOpeningDevices ,如果是删除设备,会保存到 mClosingDevices
const auto res = readNotifyLocked();
if (!res.ok()) {
ALOGW("Failed to read from inotify: %s", res.error().message().c_str());
}
// 有 inotify 数据,表明输入设备改变了,例如增删
deviceChanged = true;
}
// 在下一次的循环中,处理 mOpeningDevices 或 mClosingDevices ,会返回事件给 InputReader
if (deviceChanged) {
continue;
}
// 如果有事件,或者被管道唤醒,立即退出无限循环,让 InputReader 处理事件或者处理被唤醒的原因
if (!events.empty() || awoken) {
break;
}
// 重置索引
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll
// 以阻塞的方式,从 epoll 中等待事件
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
if (pollResult == 0) {
// Timed out.
mPendingEventCount = 0;
break;
}
if (pollResult < 0) {
// 异常 ...
} else {
// 保存从 epoll 读取到事件的数量
mPendingEventCount = size_t(pollResult);
}
}
// All done, return the number of events we read.
return events;
}
EventHub 提供数据的方式,是先从 epoll 阻塞等待获取事件,然后把事件返回给 InputReader。根据 EventHub 构造函数的分析可知,epoll 监听了 inotify,从而监听了输入设备的增删事件,epoll 还监听了管道的读端,用于唤醒 InputReader 线程来处理一些事情,例如,处理配置更新。
但是,InputReader 首次从 EventHub 获取数据时,EventHub 会扫描输入设备。这个过程很重要,因为会为输入设备建立数据。当输入设备产生事件,例如,key event 或 motion event,需要根据输入设备的参数,来决定如何处理事件。因此,接下来一篇文章,需要先分析输入设备的扫描过程。