From b865f65b71320b72a2a65376cf562e4ac570654f Mon Sep 17 00:00:00 2001 From: Luca Stefani Date: Sun, 8 Jul 2018 00:32:31 +0200 Subject: [PATCH] Add tri-state-key daemon Change-Id: I796f3853ac87df78a0d1a890c663e312a0ee2d1b --- tri-state-key/.clang-format | 11 ++ tri-state-key/Android.bp | 34 ++++++ tri-state-key/main.cpp | 147 ++++++++++++++++++++++++++ tri-state-key/tri-state-key_daemon.rc | 7 ++ tri-state-key/uevent.h | 32 ++++++ tri-state-key/uevent_listener.cpp | 114 ++++++++++++++++++++ tri-state-key/uevent_listener.h | 46 ++++++++ 7 files changed, 391 insertions(+) create mode 100644 tri-state-key/.clang-format create mode 100644 tri-state-key/Android.bp create mode 100644 tri-state-key/main.cpp create mode 100644 tri-state-key/tri-state-key_daemon.rc create mode 100644 tri-state-key/uevent.h create mode 100644 tri-state-key/uevent_listener.cpp create mode 100644 tri-state-key/uevent_listener.h diff --git a/tri-state-key/.clang-format b/tri-state-key/.clang-format new file mode 100644 index 0000000..ae4a451 --- /dev/null +++ b/tri-state-key/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: Google +AccessModifierOffset: -2 +AllowShortFunctionsOnASingleLine: Inline +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 +UseTab: Never +PenaltyExcessCharacter: 32 diff --git a/tri-state-key/Android.bp b/tri-state-key/Android.bp new file mode 100644 index 0000000..687199e --- /dev/null +++ b/tri-state-key/Android.bp @@ -0,0 +1,34 @@ +// +// Copyright (C) 2018 The LineageOS Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_binary { + name: "tri-state-key_daemon", + init_rc: ["tri-state-key_daemon.rc"], + srcs: [ + "main.cpp", + "uevent_listener.cpp", + ], + cppflags: [ + "-Wall", + "-Werror", + ], + vendor: true, + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libutils", + ], +} diff --git a/tri-state-key/main.cpp b/tri-state-key/main.cpp new file mode 100644 index 0000000..856b1de --- /dev/null +++ b/tri-state-key/main.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "tri-state-key_daemon" + +#include +#include +#include +#include +#include + +#include "uevent_listener.h" + +#define KEY_MODE_NORMAL 601 +#define KEY_MODE_VIBRATION 602 +#define KEY_MODE_SILENCE 603 + +using android::Uevent; +using android::UeventListener; + +int main() { + int err; + int uinputFd; + struct uinput_user_dev uidev {}; + UeventListener uevent_listener; + + LOG(INFO) << "Started"; + + uinputFd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (uinputFd < 0) { + LOG(ERROR) << "Unable to open uinput node"; + return 1; + } + + err = ioctl(uinputFd, UI_SET_EVBIT, EV_KEY) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_NORMAL) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_VIBRATION) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_SILENCE); + if (err != 0) { + LOG(ERROR) << "Unable to enable KEY events"; + goto out; + } + + sprintf(uidev.name, "uinput-tri-state-key"); + uidev.id.bustype = BUS_VIRTUAL; + + err = write(uinputFd, &uidev, sizeof(uidev)); + if (err < 0) { + LOG(ERROR) << "Write user device to uinput node failed"; + goto out; + } + + err = ioctl(uinputFd, UI_DEV_CREATE); + if (err < 0) { + LOG(ERROR) << "Unable to create uinput device"; + goto out; + } + + LOG(INFO) << "Successfully registered uinput-tri-state-key for KEY events"; + + uevent_listener.Poll([&uinputFd](const Uevent& uevent) { + int err; + struct input_event event {}; + + if (uevent.action != "change" || uevent.name != "soc:tri_state_key") { + return; + } + + bool none = uevent.state.find("USB=0") != std::string::npos; + bool vibration = uevent.state.find("USB_HOST=0") != std::string::npos; + bool silent = uevent.state.find("null)=0") != std::string::npos; + + int keyCode; + if (none && !vibration && !silent) { + keyCode = KEY_MODE_NORMAL; + } else if (!none && vibration && !silent) { + keyCode = KEY_MODE_VIBRATION; + } else if (!none && !vibration && silent) { + keyCode = KEY_MODE_SILENCE; + } else { + // Ignore intermediate states + return; + } + + // Report the key + event.type = EV_KEY; + event.code = keyCode; + event.value = 1; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_KEY to uinput node failed"; + return; + } + + // Force a flush with an EV_SYN + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_SYN to uinput node failed"; + return; + } + + // Report the key + event.type = EV_KEY; + event.code = keyCode; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_KEY to uinput node failed"; + return; + } + + // Force a flush with an EV_SYN + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_SYN to uinput node failed"; + return; + } + + return; + }); + +out: + // Clean up + close(uinputFd); + + // The loop can only be exited via failure or signal + return 1; +} diff --git a/tri-state-key/tri-state-key_daemon.rc b/tri-state-key/tri-state-key_daemon.rc new file mode 100644 index 0000000..8c1a0d7 --- /dev/null +++ b/tri-state-key/tri-state-key_daemon.rc @@ -0,0 +1,7 @@ +on fs + start tri-state-key_daemon + +service tri-state-key_daemon /vendor/bin/tri-state-key_daemon + class late_start + user system + group system diff --git a/tri-state-key/uevent.h b/tri-state-key/uevent.h new file mode 100644 index 0000000..ac30ee1 --- /dev/null +++ b/tri-state-key/uevent.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_UEVENT_H +#define _INIT_UEVENT_H + +#include + +namespace android { + +struct Uevent { + std::string action; + std::string name; + std::string state; +}; + +} // namespace android + +#endif diff --git a/tri-state-key/uevent_listener.cpp b/tri-state-key/uevent_listener.cpp new file mode 100644 index 0000000..46b77c8 --- /dev/null +++ b/tri-state-key/uevent_listener.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uevent_listener.h" + +#include +#include +#include +#include + +#include + +#include +#include + +namespace android { + +static void ParseEvent(const char* msg, Uevent* uevent) { + uevent->action.clear(); + uevent->name.clear(); + uevent->state.clear(); + + while (*msg) { + if (!strncmp(msg, "ACTION=", 7)) { + msg += 7; + uevent->action = msg; + } else if (!strncmp(msg, "NAME=", 5)) { + msg += 5; + uevent->name = msg; + } else if (!strncmp(msg, "STATE=", 6)) { + msg += 6; + uevent->state = msg; + } + // advance to after the next \0 + while (*msg++); + } + + LOG(DEBUG) << "ACTION=" << uevent->action << " NAME=" << uevent->name + << " STATE=" << uevent->state; +} + +UeventListener::UeventListener() { + // is 256K enough? udev uses 16MB! + device_fd_.reset(uevent_open_socket(256 * 1024, true)); + if (device_fd_ == -1) { + LOG(FATAL) << "Could not open uevent socket"; + } + + fcntl(device_fd_, F_SETFL, O_NONBLOCK); +} + +bool UeventListener::ReadUevent(Uevent* uevent) const { + char msg[UEVENT_MSG_LEN + 2]; + int n = uevent_kernel_multicast_recv(device_fd_, msg, UEVENT_MSG_LEN); + if (n <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG(ERROR) << "Error reading from Uevent Fd"; + } + return false; + } + if (n >= UEVENT_MSG_LEN) { + LOG(ERROR) << "Uevent overflowed buffer, discarding"; + // Return true here even if we discard as we may have more uevents pending and we + // want to keep processing them. + return true; + } + + msg[n] = '\0'; + msg[n + 1] = '\0'; + + ParseEvent(msg, uevent); + + return true; +} + +void UeventListener::Poll(const ListenerCallback& callback) const { + pollfd ufd; + ufd.events = POLLIN; + ufd.fd = device_fd_; + + while (true) { + ufd.revents = 0; + + int nr = poll(&ufd, 1, -1); + if (nr == 0) return; + if (nr < 0) { + PLOG(ERROR) << "poll() of uevent socket failed, continuing"; + continue; + } + if (ufd.revents & POLLIN) { + // We're non-blocking, so if we receive a poll event keep processing until + // we have exhausted all uevent messages. + Uevent uevent; + while (ReadUevent(&uevent)) { + callback(uevent); + } + } + } +} + +} // namespace android diff --git a/tri-state-key/uevent_listener.h b/tri-state-key/uevent_listener.h new file mode 100644 index 0000000..e606e7f --- /dev/null +++ b/tri-state-key/uevent_listener.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_UEVENT_LISTENER_H +#define _INIT_UEVENT_LISTENER_H + +#include + +#include + +#include "uevent.h" + +#define UEVENT_MSG_LEN 2048 + +namespace android { + +using ListenerCallback = std::function; + +class UeventListener { + public: + UeventListener(); + + void Poll(const ListenerCallback& callback) const; + + private: + bool ReadUevent(Uevent* uevent) const; + + android::base::unique_fd device_fd_; +}; + +} // namespace android + +#endif