archive
This commit is contained in:
commit
8017608861
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 CismonX
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,72 @@
|
|||
# ncurses-poll
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This repository contains a simple header, [ncurses-poll.hpp](src/ncurses-poll.hpp). With it, you can perform non-blocking I/O in your ncurses application.
|
||||
|
||||
## 2. Features
|
||||
|
||||
### 2.1 Multiple instances
|
||||
|
||||
Each instance of `ncurses_poll` wraps an ncurses `SCREEN*`. Multiple instances can be handled simultaneously in one thread.
|
||||
|
||||
Note that there's no need to call `set_term()` because this is done by the header before callbacks are invoked.
|
||||
|
||||
### 2.2 Boost.Asio and coroutines
|
||||
|
||||
Asynchronous I/O is achieved using Boost.Asio. Thus, other libraries(e.g. [Beast](https://github.com/boostorg/beast)) which uses Boost.Asio can be easily integrated into your ncurses applications.
|
||||
|
||||
Boost.Asio's stackless coroutine is also supported for cleaner and human-friendlier code.
|
||||
|
||||
## 3. Documentation
|
||||
|
||||
### 3.1 Event loop
|
||||
|
||||
An `ncurses_poll` instance must run within a Boost.Asio event loop (aka `io_context`). A const reference to an `io_context` instance should be passed to the constructor.
|
||||
|
||||
### 3.2 Session
|
||||
|
||||
The `ncurses_poll` class template requires a parameter `Session`, which is the type of session. The session may contain username, permission, or other data concerning a specific connection.
|
||||
|
||||
`Session` must be a class with a constructor which accepts a pointer to this `ncurses_poll<session>` class.
|
||||
|
||||
To get pointer to current session instance of an `ncurses_poll` instance, call `session()` method.
|
||||
|
||||
### 3.2 Async read/write
|
||||
|
||||
A read/write operation may block, so we need to determine whether the stream is readable/writable before performing I/O.
|
||||
|
||||
The `on_readable` and `on_writable` methods accept a single parameter, the const reference to a callback function. Once the stream is readable/writable, the given callback will be invoked.
|
||||
|
||||
The callback function should be of the following type:
|
||||
|
||||
```C++
|
||||
using callback =
|
||||
std::function<void(const boost::system::error_code&, ncurses_poll*)>;
|
||||
```
|
||||
|
||||
* The first argument of callback is the error code, upon success it yields zero, otherwise it is set to the corresponding errno. You should always handle `error_code` before performing I/O.
|
||||
* The second argument is the pointer to the `ncurses_poll` instance.
|
||||
|
||||
### 3.3 Using coroutines
|
||||
|
||||
Boost.Asio's stackless coroutine requires a `boost::asio::coroutine` instance. You can simply use the `coro` property of `ncurses_poll`. For example, in a callback function:
|
||||
|
||||
```C++
|
||||
if (!ec) reenter(poll->coro) for (;;) {
|
||||
yield poll->on_writable(cb);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Other operations
|
||||
|
||||
To get corresponding `io_context` instance, call `get_io_context()` method.
|
||||
|
||||
To get `SCREEN` pointer to current ncurses instance, call `screen()` method.
|
||||
|
||||
### 3.5 See also
|
||||
|
||||
* [Examples](/examples) for using `ncurses_poll`.
|
||||
* [Ncurses Programming Guide](http://www.cs.ukzn.ac.za/~hughm/os/notes/ncurses.html).
|
||||
* Boost.Asio [documentation](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio.html).
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// examples/with-timer.cpp
|
||||
//
|
||||
// A simple example for ncurses-poll in which user input is displayed
|
||||
// on the first line, while the current timestamp on the second, which
|
||||
// is refreshed once per second.
|
||||
//
|
||||
// @author CismonX <admin@cismon.net>
|
||||
//
|
||||
|
||||
#include "ncurses-poll.hpp"
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
struct my_session {
|
||||
int chr = 0;
|
||||
int num_chr = 0;
|
||||
boost::asio::deadline_timer timer;
|
||||
my_session(ncurses_poll<my_session>* ncp) : timer(*ncp->get_io_context()) {}
|
||||
};
|
||||
|
||||
using boost::system::error_code;
|
||||
using ncp = ncurses_poll<my_session>;
|
||||
using callback = std::function<void(const boost::system::error_code&, ncp*)>;
|
||||
|
||||
void tick(const error_code&, ncp*);
|
||||
|
||||
inline void defer(ncp* poll) {
|
||||
poll->session()->timer.expires_from_now(boost::posix_time::seconds(1));
|
||||
poll->session()->timer.async_wait(boost::bind(tick, boost::asio::placeholders::error, poll));
|
||||
}
|
||||
|
||||
void tick(const error_code& ec, ncp* poll) {
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
poll->on_writable([](const error_code& ec, ncp* poll) {
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
mvaddstr(1, 0, std::to_string(std::time(nullptr)).c_str());
|
||||
refresh();
|
||||
defer(poll);
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
boost::asio::io_context io_context;
|
||||
ncp poll(io_context);
|
||||
poll.init();
|
||||
callback cb = [&cb](const error_code& ec, ncp* poll) {
|
||||
if (!ec) reenter(poll->coro) for (;;) {
|
||||
poll->session()->chr = getch() | A_UNDERLINE;
|
||||
yield poll->on_writable(cb);
|
||||
mvaddch(0, poll->session()->num_chr++, poll->session()->chr);
|
||||
refresh();
|
||||
yield poll->on_readable(cb);
|
||||
}
|
||||
};
|
||||
poll.on_writable([&cb](const error_code& ec, ncp* poll) {
|
||||
if (!ec) {
|
||||
curs_set(0);
|
||||
cbreak();
|
||||
noecho();
|
||||
refresh();
|
||||
poll->on_readable(cb);
|
||||
}
|
||||
});
|
||||
defer(&poll);
|
||||
io_context.run();
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
//
|
||||
// ncurses-poll.hpp
|
||||
//
|
||||
// A header for performing asynchronous I/O with ncurses using Boost.Asio.
|
||||
//
|
||||
// @author CismonX <admin@cismon.net>
|
||||
//
|
||||
|
||||
#ifndef NCURSES_POLL_HPP_
|
||||
#define NCURSES_POLL_HPP_
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <curses.h>
|
||||
|
||||
template <typename Session>
|
||||
class ncurses_poll {
|
||||
|
||||
using stream_descriptor_t = boost::asio::posix::stream_descriptor;
|
||||
|
||||
/// Provides asynchronous I/O functionalities for this ncurses instance.
|
||||
stream_descriptor_t sd_;
|
||||
|
||||
/// I/O context used by the stream descriptor.
|
||||
boost::asio::io_context *context_;
|
||||
|
||||
/// File pointer of this tty.
|
||||
FILE *file_ = nullptr;
|
||||
|
||||
/// Screen pointer of this ncurses instance.
|
||||
SCREEN *screen_ = nullptr;
|
||||
|
||||
/// User-defined session for this ncurses instance.
|
||||
Session session_;
|
||||
|
||||
public:
|
||||
/// Provides support for stackless coroutines.
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param io_context The I/O context used for dispatching handlers.
|
||||
*/
|
||||
explicit ncurses_poll(boost::asio::io_context& io_context) :
|
||||
sd_(io_context), context_(&io_context), session_(this) {}
|
||||
|
||||
/**
|
||||
* Deleted default constructor.
|
||||
*/
|
||||
ncurses_poll() = delete;
|
||||
|
||||
/**
|
||||
* Initialize ncurses instance.
|
||||
*
|
||||
* @param path File path. e.g. "/dev/pts/0". Empty for current tty.
|
||||
*/
|
||||
int init(const std::string& path = ttyname(STDIN_FILENO)) {
|
||||
file_ = fopen(path.c_str(), "r+");
|
||||
if (file_ == nullptr) {
|
||||
return errno;
|
||||
}
|
||||
screen_ = newterm(nullptr, file_, file_);
|
||||
boost::system::error_code ec;
|
||||
sd_.assign(fileno(file_), ec);
|
||||
return ec.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given callback once the terminal is readable.
|
||||
*
|
||||
* @param read_cb The callback to be invoked.
|
||||
*/
|
||||
template <typename F>
|
||||
void on_readable(F&& read_cb) {
|
||||
sd_.async_wait(stream_descriptor_t::wait_read, boost::bind<void>(
|
||||
[this, read_cb](const boost::system::error_code& ec) {
|
||||
set_term(screen_);
|
||||
read_cb(ec, this);
|
||||
}, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given callback once the terminal is writable.
|
||||
*
|
||||
* @param write_cb The callback to be invoked.
|
||||
*/
|
||||
template <typename F>
|
||||
void on_writable(F&& write_cb) {
|
||||
sd_.async_wait(stream_descriptor_t::wait_write, boost::bind<void>(
|
||||
[this, write_cb](const boost::system::error_code& ec) {
|
||||
set_term(screen_);
|
||||
write_cb(ec, this);
|
||||
}, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current session.
|
||||
*/
|
||||
Session *session() {
|
||||
return &session_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get screen pointer of this ncurses instance.
|
||||
*/
|
||||
SCREEN *screen() const {
|
||||
return screen_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current I/O context.
|
||||
*/
|
||||
boost::asio::io_context *get_io_context() {
|
||||
return context_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~ncurses_poll() {
|
||||
if (screen_) {
|
||||
set_term(screen_);
|
||||
endwin();
|
||||
delscreen(screen_);
|
||||
}
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue