diff options
Diffstat (limited to 'client/x11/event_sources_p.cpp')
-rw-r--r-- | client/x11/event_sources_p.cpp | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/client/x11/event_sources_p.cpp b/client/x11/event_sources_p.cpp new file mode 100644 index 00000000..2e6af138 --- /dev/null +++ b/client/x11/event_sources_p.cpp @@ -0,0 +1,339 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/epoll.h> +#include <sys/fcntl.h> + +#include "event_sources.h" +#include "debug.h" +#include "utils.h" + +#ifdef USING_EVENT_FD +#include <sys/eventfd.h> +#endif + +#define NUM_EPOLL_EVENTS 10 + +#ifdef USING_EVENT_FD +#define WRITE_FD _event_fd +#define EVENT_DATA_TYPE eventfd_t +#else +#define WRITE_FD _event_write_fd +#define EVENT_DATA_TYPE uint8_t +#endif + +class EventWrapper { +public: + EventWrapper(EventSources& owner, EventSource& event) + : _owner (owner) + , _event (&event) + , _refs (1) + { + } + + EventWrapper* ref() + { + _refs++; + return this; + } + + void unref() + { + if (!--_refs) { + _owner.remove_wrapper(this); + delete this; + } + } + + EventSource* get_event() + { + return _event; + } + + void invalidate() + { + _event = NULL; + } + +private: + EventSources& _owner; + EventSource* _event; + int _refs; +}; + +EventSources::EventSources() +{ + _epoll = epoll_create(NUM_EPOLL_EVENTS); + if (_epoll == -1) { + THROW("create epool failed"); + } +} + +EventSources::~EventSources() +{ + Events::iterator iter = _events.begin(); + for (; iter != _events.end(); iter++) { + delete *iter; + } + close(_epoll); +} + +bool EventSources::wait_events(int timeout_ms) +{ + struct epoll_event events[NUM_EPOLL_EVENTS]; + int num_events = epoll_wait(_epoll, events, NUM_EPOLL_EVENTS, timeout_ms); + + if (num_events == -1) { + if (errno == EINTR) { + return false; + } + THROW("wait error eventfd failed"); + } + + for (int i = 0; i < num_events; i++) { + ((EventWrapper*)events[i].data.ptr)->ref(); + } + + for (int i = 0; i < num_events; i++) { + EventWrapper* wrapper; + EventSource* event; + + wrapper = (EventWrapper *)events[i].data.ptr; + if ((event = wrapper->get_event())) { + event->action(); + } + wrapper->unref(); + } + return false; +} + +void EventSources::add_trigger(Trigger& trigger) +{ + int fd = trigger.get_fd(); + EventWrapper* wrapper = new EventWrapper(*this, trigger); + struct epoll_event event; + event.data.ptr = wrapper; + event.events = EPOLLIN; + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, &event) == -1) { + THROW("epoll add failed"); + } + _events.push_back(wrapper); +} + +void EventSources_p::remove_wrapper(EventWrapper* wrapper) +{ + Events::iterator iter = _events.begin(); + for (;; iter++) { + if (iter == _events.end()) { + THROW("wrapper not found"); + } + if ((*iter) == wrapper) { + _events.erase(iter); + return; + } + } +} + +void EventSources::remove_trigger(Trigger& trigger) +{ + Events::iterator iter = _events.begin(); + for (;; iter++) { + if (iter == _events.end()) { + THROW("trigger not found"); + } + if ((*iter)->get_event() == &trigger) { + (*iter)->invalidate(); + (*iter)->unref(); + break; + } + } + int fd = trigger.get_fd(); + if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { + THROW("epoll remove failed"); + } +} + +EventSources::Trigger::Trigger() +{ +#ifdef USING_EVENT_FD + _event_fd = eventfd(0, 0); + if (_event_fd == -1) { + THROW("create eventfd failed"); + } +#else + int fd[2]; + if (pipe(fd) == -1) { + THROW("create pipe failed"); + } + _event_fd = fd[0]; + _event_write_fd = fd[1]; +#endif + int flags; + if ((flags = fcntl(_event_fd, F_GETFL)) == -1) { + THROW("failed to set eventfd non block: %s", strerror(errno)); + } + + if (fcntl(_event_fd, F_SETFL, flags | O_NONBLOCK) == -1) { + THROW("failed to set eventfd non block: %s", strerror(errno)); + } +} + +EventSources::Trigger::~Trigger() +{ + close(_event_fd); +#ifndef USING_EVENT_FD + close(_event_write_fd); +#endif +} + +void EventSources::Trigger::trigger() +{ + Lock lock(_lock); + if (_pending_int) { + return; + } + _pending_int = true; + static const EVENT_DATA_TYPE val = 1; + if (::write(WRITE_FD, &val, sizeof(val)) != sizeof(val)) { + THROW("write event failed"); + } +} + +bool Trigger_p::reset_event() +{ + Lock lock(_lock); + if (!_pending_int) { + return false; + } + EVENT_DATA_TYPE val; + if (read(_event_fd, &val, sizeof(val)) != sizeof(val)) { + THROW("event read error"); + } + _pending_int = false; + return true; +} + +void EventSources::Trigger::reset() +{ + reset_event(); +} + +void EventSources::Trigger::action() +{ + if (reset_event()) { + on_event(); + } +} + +static void set_non_blocking(int fd) +{ + int flags; + if ((flags = fcntl(fd, F_GETFL)) == -1) { + THROW("failed to set socket non block: %s", strerror(errno)); + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + THROW("failed to set socket non block: %s", strerror(errno)); + } +} + +static void set_blocking(int fd) +{ + int flags; + if ((flags = fcntl(fd, F_GETFL)) == -1) { + THROW("failed to clear socket non block: %s", strerror(errno)); + } + + if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) { + THROW("failed to clear socket non block: %s", strerror(errno)); + } +} + +static void add_to_poll(int fd, int epoll, EventWrapper* wrapper) +{ + struct epoll_event event; + event.data.ptr = wrapper; + event.events = EPOLLIN | EPOLLOUT | EPOLLET; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &event) == -1) { + THROW("epoll add failed"); + } +} + +void EventSources::add_socket(Socket& socket) +{ + int fd = socket.get_socket(); + set_non_blocking(fd); + EventWrapper* wrapper = new EventWrapper(*this, socket); + add_to_poll(fd, _epoll, wrapper); + _events.push_back(wrapper); +} + +static bool remove_event(EventSources_p::Events& events, EventSource& event) +{ + EventSources_p::Events::iterator iter = events.begin(); + for (;; iter++) { + if (iter == events.end()) { + return false; + } + if ((*iter)->get_event() == &event) { + (*iter)->invalidate(); + (*iter)->unref(); + return true; + } + } +} + +void EventSources::remove_socket(Socket& socket) +{ + if (!remove_event(_events, socket)) { + THROW("socket not found"); + } + int fd = socket.get_socket(); + if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { + THROW("epoll remove failed"); + } + set_blocking(fd); +} + +void EventSources::add_file(File& file) +{ + int fd = file.get_fd(); + set_non_blocking(fd); + EventWrapper* wrapper = new EventWrapper(*this, file); + add_to_poll(fd, _epoll, wrapper); + _events.push_back(wrapper); +} + +void EventSources::remove_file(File& file) +{ + if (!remove_event(_events, file)) { + THROW("file not found"); + } + int fd = file.get_fd(); + if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { + THROW("epoll remove failed"); + } + set_blocking(fd); +} + +void EventSources::add_handle(Handle& file) +{ +} + +void EventSources::remove_handle(Handle& file) +{ +} |