#include "input-monitor-config.hpp"
#include "input-monitor.hpp"
+#include "zones-manager.hpp"
#include "exception.hpp"
#include "logger/logger.hpp"
#include "utils/exception.hpp"
#include "utils/fs.hpp"
-#include "utils/callback-wrapper.hpp"
-#include "utils/scoped-gerror.hpp"
+#include "utils/fd-utils.hpp"
#include <cassert>
#include <cstring>
#include <fcntl.h>
-#include <functional>
-#include <glib.h>
#include <linux/input.h>
#include <sys/stat.h>
#include <sys/types.h>
} // namespace
-InputMonitor::InputMonitor(const InputConfig& inputConfig,
- const NotifyCallback& notifyCallback)
- : mConfig(inputConfig),
- mNotifyCallback(notifyCallback)
+InputMonitor::InputMonitor(ipc::epoll::EventPoll& eventPoll,
+ const InputConfig& inputConfig,
+ ZonesManager* zonesManager)
+ : mConfig(inputConfig)
+ , mZonesManager(zonesManager)
+ , mFd(-1)
+ , mEventPoll(eventPoll)
{
if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) {
LOGE("Time window exceeds maximum: " << MAX_TIME_WINDOW_SEC);
throw InputMonitorException("Number of events exceeds maximum");
}
- std::string devicePath = getDevicePath();
+ mDevicePath = getDevicePath();
LOGT("Input monitor configuration: \n"
<< "\tenabled: " << mConfig.enabled << "\n"
<< "\tdevice: " << mConfig.device << "\n"
- << "\tpath: " << devicePath << "\n"
+ << "\tpath: " << mDevicePath << "\n"
<< "\ttype: " << EV_KEY << "\n"
<< "\tcode: " << mConfig.code << "\n"
<< "\tvalue: " << KEY_PRESSED << "\n"
<< "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n"
<< "\ttimeWindowMs: " << mConfig.timeWindowMs);
-
- createGIOChannel(devicePath);
}
InputMonitor::~InputMonitor()
{
+ std::unique_lock<std::mutex> mMutex;
LOGD("Destroying InputMonitor");
- ScopedGError error;
- g_io_channel_unref(mChannelPtr);
- if (g_io_channel_shutdown(mChannelPtr, FALSE, &error)
- != G_IO_STATUS_NORMAL) {
- LOGE("Error during shutting down GIOChannel: " << error->message);
- }
+ stop();
+}
- if (!g_source_remove(mSourceId)) {
- LOGE("Error during removing the source");
- }
+void InputMonitor::start()
+{
+ std::unique_lock<Mutex> mMutex;
+ setHandler(mDevicePath);
+}
+
+void InputMonitor::stop()
+{
+ leaveDevice();
}
namespace {
+
bool isDeviceWithName(const boost::regex& deviceNameRegex,
const fs::directory_entry& directoryEntry)
{
return false;
}
- int fd = open(path.c_str(), O_RDONLY);
+ int fd = ::open(path.c_str(), O_RDONLY);
if (fd < 0) {
LOGD("Failed to open " << path);
return false;
char name[DEVICE_NAME_LENGTH];
if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
LOGD("Failed to get the device name of: " << path);
- if (close(fd) < 0) {
+ if (::close(fd) < 0) {
LOGE("Error during closing file " << path);
}
return false;
}
- if (close(fd) < 0) {
+ if (::close(fd) < 0) {
LOGE("Error during closing file " << path);
}
return false;
}
+
} // namespace
std::string InputMonitor::getDevicePath() const
return it->path().string();
}
-
-
-void InputMonitor::createGIOChannel(const std::string& devicePath)
+void InputMonitor::setHandler(const std::string& devicePath)
{
+ using namespace std::placeholders;
+
// We need NONBLOCK for FIFOs in the tests
- int fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK);
- if (fd < 0) {
+ mFd = ::open(devicePath.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (mFd < 0) {
LOGE("Cannot create input monitor channel. Device file: " <<
devicePath << " doesn't exist");
throw InputMonitorException("Device does not exist");
}
-
- mChannelPtr = g_io_channel_unix_new(fd);
-
- // Read binary data
- if (g_io_channel_set_encoding(mChannelPtr,
- NULL,
- NULL) != G_IO_STATUS_NORMAL) {
- LOGE("Cannot set encoding for input monitor channel ");
- throw InputMonitorException("Cannot set encoding");
- }
-
- using namespace std::placeholders;
- ReadDeviceCallback callback = std::bind(&InputMonitor::readDevice, this, _1);
- // Add the callback
- mSourceId = g_io_add_watch_full(mChannelPtr,
- G_PRIORITY_DEFAULT,
- G_IO_IN,
- readDeviceCallback,
- utils::createCallbackWrapper(callback, mGuard.spawn()),
- &utils::deleteCallbackWrapper<ReadDeviceCallback>);
- if (!mSourceId) {
- LOGE("Cannot add watch on device input file");
- throw InputMonitorException("Cannot add watch");
- }
+ mEventPoll.addFD(mFd, EPOLLIN, std::bind(&InputMonitor::handleInternal, this, _1, _2));
}
-gboolean InputMonitor::readDeviceCallback(GIOChannel* gio,
- GIOCondition /*condition*/,
- gpointer data)
-{
- const ReadDeviceCallback& callback = utils::getCallbackFromPointer<ReadDeviceCallback>(data);
- callback(gio);
- return TRUE;
-}
-
-void InputMonitor::readDevice(GIOChannel* gio)
+void InputMonitor::handleInternal(int /* fd */, ipc::epoll::Events events)
{
struct input_event ie;
- gsize nBytesReadTotal = 0;
- gsize nBytesRequested = sizeof(struct input_event);
- ScopedGError error;
-
- do {
- gsize nBytesRead = 0;
- GIOStatus readStatus = g_io_channel_read_chars(gio,
- &reinterpret_cast<gchar*>(&ie)[nBytesReadTotal],
- nBytesRequested,
- &nBytesRead,
- &error);
-
- if (readStatus == G_IO_STATUS_ERROR) {
- LOGE("Read from input monitor channel failed: " << error->message);
+ try {
+ if (events == EPOLLHUP) {
+ stop();
return;
}
-
- nBytesRequested -= nBytesRead;
- nBytesReadTotal += nBytesRead;
- } while (nBytesRequested > 0);
-
-
+ utils::read(mFd, &ie, sizeof(struct input_event));
+ } catch (const std::exception& ex) {
+ LOGE("Read from input monitor channel failed: " << ex.what());
+ return;
+ }
if (isExpectedEventSequence(ie)) {
LOGI("Input monitor detected pattern.");
- mNotifyCallback();
+ if (mZonesManager->isRunning()) {
+ mZonesManager->switchingSequenceMonitorNotify();
+ }
+ }
+}
+
+void InputMonitor::leaveDevice()
+{
+ if (mFd != -1) {
+ mEventPoll.removeFD(mFd);
+ utils::close(mFd);
+ mFd = -1;
}
}
#define SERVER_INPUT_MONITOR_HPP
#include "input-monitor-config.hpp"
-#include "utils/callback-guard.hpp"
+#include "ipc/epoll/event-poll.hpp"
#include <linux/input.h>
#include <sys/time.h>
-#include <glib.h>
-#include <functional>
#include <string>
#include <list>
+#include <mutex>
namespace vasum {
+class ZonesManager;
+
class InputMonitor {
public:
- typedef std::function<void()> NotifyCallback;
-
- InputMonitor(const InputConfig& inputConfig,
- const NotifyCallback& notifyCallback);
+ InputMonitor(ipc::epoll::EventPoll& eventPoll,
+ const InputConfig& inputConfig,
+ ZonesManager* zonesManager);
~InputMonitor();
+ void start();
+ void stop();
private:
- typedef std::function<void(GIOChannel* gio)> ReadDeviceCallback;
+ typedef std::mutex Mutex;
InputConfig mConfig;
- NotifyCallback mNotifyCallback;
-
+ ZonesManager* mZonesManager;
+ int mFd;
+ ipc::epoll::EventPoll& mEventPoll;
std::list<struct timeval> mEventTimes;
- GIOChannel* mChannelPtr;
+ std::string mDevicePath;
+ Mutex mMutex;
std::string getDevicePath() const;
- void createGIOChannel(const std::string& devicePath);
-
- // Internal callback to be registered at glib g_io_add_watch()
- static gboolean readDeviceCallback(GIOChannel*, GIOCondition, gpointer);
+ void setHandler(const std::string& devicePath);
+ void handleInternal(int fd, ipc::epoll::Events events);
+ void leaveDevice();
bool isExpectedEventSequence(const struct input_event&);
- void readDevice(GIOChannel*);
- utils::CallbackGuard mGuard;
- guint mSourceId;
};
} // namespace vasum
configPath,
mDynamicConfig,
getVasumDbPrefix());
+
+ if (mConfig.inputConfig.enabled) {
+ LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
+ mSwitchingSequenceMonitor.reset(new InputMonitor(eventPoll, mConfig.inputConfig, this));
+ }
}
ZonesManager::~ZonesManager()
LOGD("ZonesManager object instantiated");
if (mConfig.inputConfig.enabled) {
- LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
- mSwitchingSequenceMonitor.reset(
- new InputMonitor(mConfig.inputConfig,
- std::bind(&ZonesManager::switchingSequenceMonitorNotify,
- this)));
+ LOGI("Starting input monitor ");
+ mSwitchingSequenceMonitor->start();
}
// After everything's initialized start to respond to clients' requests
// wait for all tasks to complete
mWorker.reset();
mHostIPCConnection.stop(wait);
+ if (mConfig.inputConfig.enabled) {
+ LOGI("Stopping input monitor ");
+ mSwitchingSequenceMonitor->stop();
+ }
mIsRunning = false;
}
api::MethodResultBuilder::Pointer result);
void handleCleanUpZonesRootCall(api::MethodResultBuilder::Pointer result);
+ void switchingSequenceMonitorNotify();
+
private:
typedef std::recursive_mutex Mutex;
typedef std::unique_lock<Mutex> Lock;
void saveDynamicConfig();
void updateDefaultId();
void refocus();
- void switchingSequenceMonitorNotify();
void generateNewConfig(const std::string& id,
const std::string& templatePath);
std::string getTemplatePathForExistingZone(const std::string& id);
#include "input-monitor.hpp"
#include "input-monitor-config.hpp"
#include "exception.hpp"
+#include "zones-manager.hpp"
#include "utils/glib-loop.hpp"
#include "utils/latch.hpp"
#include "utils/scoped-dir.hpp"
+#include "ipc/epoll/thread-dispatcher.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <memory>
#include <string>
-
using namespace vasum;
using namespace utils;
const int EVENT_BUTTON_RELEASED = 0;
const int EVENT_BUTTON_PRESSED = 1;
-const int SINGLE_EVENT_TIMEOUT = 1000;
+const int EVENT_TIMEOUT = 1000;
+
+const std::string CONFIG_DIR = VSM_TEST_CONFIG_INSTALL_DIR;
+const std::string TEST_CONFIG_PATH = CONFIG_DIR + "/test-daemon.conf";
+const std::string SIMPLE_TEMPLATE = "console-ipc";
+const std::string ZONES_PATH = "/tmp/ut-zones"; // the same as in daemon.conf
struct Fixture {
utils::ScopedGlibLoop mLoop;
ScopedDir mTestPathGuard;
+ ScopedDir mZonesPathGuard;
InputConfig inputConfig;
struct input_event ie;
+ ipc::epoll::ThreadDispatcher dispatcher;
Fixture()
: mTestPathGuard(TEST_DIR)
+ , mZonesPathGuard(ZONES_PATH)
{
inputConfig.numberOfEvents = 2;
inputConfig.device = TEST_INPUT_DEVICE;
BOOST_CHECK(::mkfifo(TEST_INPUT_DEVICE.c_str(), S_IWUSR | S_IRUSR) >= 0);
}
+
+ ipc::epoll::EventPoll& getPoll() {
+ return dispatcher.getPoll();
+ }
};
+
+template<class Predicate>
+bool spinWaitFor(int timeoutMs, Predicate pred)
+{
+ auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs);
+ while (!pred()) {
+ if (std::chrono::steady_clock::now() >= until) {
+ return false;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ return true;
+}
+
} // namespace
BOOST_FIXTURE_TEST_SUITE(InputMonitorSuite, Fixture)
BOOST_AUTO_TEST_CASE(ConfigOK)
{
- InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback());
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+ InputMonitor inputMonitor(getPoll(), inputConfig, &cm);
}
BOOST_AUTO_TEST_CASE(ConfigTimeWindowMsTooHigh)
{
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
inputConfig.timeWindowMs = 50000;
- BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
+ BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm),
InputMonitorException,
WhatEquals("Time window exceeds maximum"));
}
BOOST_AUTO_TEST_CASE(ConfigDeviceFilePathNotExisting)
{
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
inputConfig.device = TEST_INPUT_DEVICE + "notExisting";
- BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
+ BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm),
InputMonitorException,
WhatEquals("Cannot find a device"));
}
namespace {
-void sendNEvents(Fixture& f, unsigned int noOfEventsToSend)
+void sendEvent(Fixture& f, ZonesManager& cm)
{
- Latch eventLatch;
-
- InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();});
-
+ InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm);
+ inputMonitor.start();
int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
BOOST_REQUIRE(fd >= 0);
- for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
+ for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) {
// button pressed event
f.ie.value = EVENT_BUTTON_PRESSED;
f.ie.time.tv_usec += 5;
}
BOOST_CHECK(::close(fd) >= 0);
- BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
-
- // Check if no more events are waiting
- BOOST_CHECK(!eventLatch.wait(10));
}
} // namespace
-BOOST_AUTO_TEST_CASE(EventOneAtATime)
+BOOST_AUTO_TEST_CASE(SingleEvent)
{
- sendNEvents(*this, 1);
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+ cm.start();
+ cm.createZone("zone1", SIMPLE_TEMPLATE);
+ cm.createZone("zone2", SIMPLE_TEMPLATE);
+ cm.createZone("zone3", SIMPLE_TEMPLATE);
+ cm.restoreAll();
+ sendEvent(*this, cm);
+ BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+ return cm.getRunningForegroundZoneId() == "zone2";
+ }));
}
-BOOST_AUTO_TEST_CASE(EventTenAtATime)
+BOOST_AUTO_TEST_CASE(MultipleEvent)
{
- sendNEvents(*this, 10);
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+ cm.start();
+ cm.createZone("zone1", SIMPLE_TEMPLATE);
+ cm.createZone("zone2", SIMPLE_TEMPLATE);
+ cm.createZone("zone3", SIMPLE_TEMPLATE);
+ cm.restoreAll();
+ for (int i = 1; i < 10; ++i) {
+ sendEvent(*this, cm);
+ std::string zoneId = "zone" + std::to_string(i % 3 + 1);
+ BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+ return cm.getRunningForegroundZoneId() == zoneId;
+ }));
+ }
}
namespace {
-void sendNEventsWithPauses(Fixture& f, unsigned int noOfEventsToSend)
+void sendEventWithPauses(Fixture& f, ZonesManager& cm)
{
- Latch eventLatch;
-
- InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();});
+ InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm);
+ inputMonitor.start();
int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
BOOST_REQUIRE(fd >= 0);
- for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
+ for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) {
// Send first two bytes of the button pressed event
f.ie.value = EVENT_BUTTON_PRESSED;
f.ie.time.tv_usec += 5;
BOOST_CHECK(ret > 0);
}
BOOST_CHECK(::close(fd) >= 0);
- BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
-
- // Check if no more events are waiting
- BOOST_CHECK(!eventLatch.wait(10));
}
} // namespace
-BOOST_AUTO_TEST_CASE(EventOneAtATimeWithPauses)
+BOOST_AUTO_TEST_CASE(SingleEventWithPauses)
{
- sendNEventsWithPauses(*this, 1);
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+ cm.start();
+ cm.createZone("zone1", SIMPLE_TEMPLATE);
+ cm.createZone("zone2", SIMPLE_TEMPLATE);
+ cm.createZone("zone3", SIMPLE_TEMPLATE);
+ cm.restoreAll();
+ sendEventWithPauses(*this, cm);
+ BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+ return cm.getRunningForegroundZoneId() == "zone2";
+ }));
}
-BOOST_AUTO_TEST_CASE(EventTenAtATimeWithPauses)
+BOOST_AUTO_TEST_CASE(MultipleEventWithPauses)
{
- sendNEventsWithPauses(*this, 10);
+ ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+ cm.start();
+ cm.createZone("zone1", SIMPLE_TEMPLATE);
+ cm.createZone("zone2", SIMPLE_TEMPLATE);
+ cm.createZone("zone3", SIMPLE_TEMPLATE);
+ cm.restoreAll();
+ for (int i = 1; i < 10; ++i) {
+ sendEventWithPauses(*this, cm);
+ std::string zoneId = "zone" + std::to_string(i % 3 + 1);
+ BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+ return cm.getRunningForegroundZoneId() == zoneId;
+ }));
+ }
}