diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/args.cc | 323 | ||||
| -rw-r--r-- | src/args.hh | 64 | ||||
| -rw-r--r-- | src/clock.cc | 24 | ||||
| -rw-r--r-- | src/clock.hh | 13 | ||||
| -rw-r--r-- | src/common.hh | 10 | ||||
| -rw-r--r-- | src/fake_monitor.cc | 191 | ||||
| -rw-r--r-- | src/fake_monitor.hh | 11 | ||||
| -rw-r--r-- | src/io.cc | 167 | ||||
| -rw-r--r-- | src/io.hh | 87 | ||||
| -rw-r--r-- | src/looper.cc | 10 | ||||
| -rw-r--r-- | src/looper.hh | 31 | ||||
| -rw-r--r-- | src/main.cc | 872 | ||||
| -rw-r--r-- | src/monitor.cc | 410 | ||||
| -rw-r--r-- | src/monitor.hh | 62 | ||||
| -rw-r--r-- | src/poll_looper.cc | 249 | ||||
| -rw-r--r-- | src/poll_looper.hh | 15 | ||||
| -rw-r--r-- | src/x.cc | 312 | ||||
| -rw-r--r-- | src/x.hh | 255 |
18 files changed, 3106 insertions, 0 deletions
diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..9b3e409 --- /dev/null +++ b/src/args.cc @@ -0,0 +1,323 @@ +#include "common.hh" + +#include <string.h> +#include <string> +#include <sys/ioctl.h> +#include <unistd.h> +#include <unordered_map> +#include <vector> + +#include "args.hh" + +namespace { + +bool is_space(std::string const& str, size_t offset) { + return str[offset] == ' ' || str[offset] == '\t' || str[offset] == '\n'; +} + +bool is_separator(std::string const& str, size_t offset) { + return !((str[offset] >= '0' && str[offset] <= '9') + || (str[offset] >= 'a' && str[offset] <= 'z') + || (str[offset] >= 'A' && str[offset] <='Z')); +} + +class ArgsImpl : public Args { +public: + ArgsImpl() + : good_(true) { + } + void add(char short_opt, std::string const& long_opt, + std::string const& argument, std::string const& help) override { + assert(short_opt == '\0' || short_opts_.count(short_opt) == 0); + assert(long_opt.empty() || long_opts_.count(long_opt) == 0); + assert(long_opt.find('=') == std::string::npos); + auto const index = opts_.size(); + opts_.push_back(Option(short_opt, long_opt, argument, help)); + if (short_opt != '\0') { + short_opts_.insert(std::make_pair(short_opt, index)); + } + if (!long_opt.empty()) { + long_opts_.insert(std::make_pair(long_opt, index)); + } + } + + bool run(int argc, char** argv, std::ostream& out) override { + if (argc == 0) { + assert(false); + good_ = false; + return false; + } + auto start = strrchr(argv[0], '/'); + if (!start) { + start = argv[0]; + } else { + start++; + } + return run(start, argc, argv, out); + } + + bool run(std::string const& prg, int argc, char** argv, std::ostream& out) + override { + reset(); + + std::string opt; + for (int a = 1; a < argc; ++a) { + if (argv[a][0] == '-') { + if (argv[a][1] == '-') { + if (argv[a][2] == '\0') { + for (++a; a < argc; ++a) { + args_.push_back(argv[a]); + } + return good_; + } + size_t len = 2; + while (argv[a][len] && argv[a][len] != '=') ++len; + opt.assign(argv[a] + 2, len - 2); + auto i = long_opts_.find(opt); + if (i == long_opts_.end()) { + out << prg << ": unrecognized option '--" << opt << "'\n"; + good_ = false; + continue; + } + if (argv[a][len] == '=') { + if (opts_[i->second].argument.empty()) { + out << prg << ": option '--" << opt << "'" + << " doesn't allow an argument\n"; + good_ = false; + continue; + } else { + opts_[i->second].values.push_back(argv[a] + len + 1); + } + } else { + if (opts_[i->second].argument.empty()) { + opts_[i->second].values.push_back(""); + } else if (a + 1 == argc) { + out << prg << ": option '--" << opt << "'" + << " requires an argument\n"; + good_ = false; + continue; + } else { + opts_[i->second].values.push_back(argv[++a]); + } + } + } else { + for (auto opt = argv[a] + 1; *opt; ++opt) { + auto i = short_opts_.find(*opt); + if (i == short_opts_.end()) { + out << prg << ": invalid option -- '" << *opt << "'\n"; + good_ = false; + continue; + } + if (opts_[i->second].argument.empty()) { + opts_[i->second].values.push_back(""); + } else if (a + 1 == argc) { + out << prg << ": option requires an argument " + << " -- '" << *opt << "'\n"; + good_ = false; + continue; + } else { + opts_[i->second].values.push_back(argv[++a]); + } + } + } + } else { + args_.push_back(argv[a]); + } + } + + return good_; + } + bool good() const override { + return good_; + } + + bool is_set(char short_opt) const override { + return count(short_opt) > 0; + } + size_t count(char short_opt) const override { + auto i = short_opts_.find(short_opt); + if (i == short_opts_.end()) return 0; + return opts_[i->second].values.size(); + } + + bool is_set(std::string const& long_opt) const override { + return count(long_opt) > 0; + } + size_t count(std::string const& long_opt) const override { + auto i = long_opts_.find(long_opt); + if (i == long_opts_.end()) return 0; + return opts_[i->second].values.size(); + } + + char const* arg(char short_opt, char const* fallback) const override { + auto i = short_opts_.find(short_opt); + if (i == short_opts_.end()) return fallback; + if (opts_[i->second].values.empty()) return fallback; + if (opts_[i->second].argument.empty()) return fallback; + return opts_[i->second].values.back().c_str(); + } + + char const* arg(std::string const& long_opt, + char const* fallback) const override { + auto i = long_opts_.find(long_opt); + if (i == long_opts_.end()) return fallback; + if (opts_[i->second].values.empty()) return fallback; + if (opts_[i->second].argument.empty()) return fallback; + return opts_[i->second].values.back().c_str(); + } + + bool args(char short_opt, std::vector<std::string>* out) const override { + auto i = short_opts_.find(short_opt); + if (i == short_opts_.end()) return false; + if (opts_[i->second].values.empty()) return false; + if (opts_[i->second].argument.empty()) return false; + if (out) { + *out = opts_[i->second].values; + } + return true; + } + + bool args(std::string const& long_opt, + std::vector<std::string>* out) const override { + auto i = long_opts_.find(long_opt); + if (i == long_opts_.end()) return false; + if (opts_[i->second].values.empty()) return false; + if (opts_[i->second].argument.empty()) return false; + if (out) { + *out = opts_[i->second].values; + } + return true; + } + + std::vector<std::string> const& arguments() const override { + return args_; + } + + void print_help(std::ostream& out) const override { + struct winsize size; + memset(&size, 0, sizeof(size)); + ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); + if (size.ws_col == 0) { + print_help(out, 80); + } else { + print_help(out, size.ws_col); + } + } + + void print_help(std::ostream& out, size_t width) const override { + size_t left = 0; + for (auto const& opt : opts_) { + size_t l = 0; + if (!opt.long_opt.empty()) { + l += 6 + opt.long_opt.size(); + } else if (opt.short_opt != '\0') { + l += 2; + } else { + continue; + } + if (!opt.argument.empty()) { + l += 1 + opt.argument.size(); + } + if (l > left) left = l; + } + + size_t const need = 2 + 2 + left; + if (need + 10 > width) { + width = need + 10; + } + size_t const right = width - need; + + for (auto const& opt : opts_) { + size_t i = 0; + if (!opt.long_opt.empty()) { + if (opt.short_opt != '\0') { + out << " -" << opt.short_opt << ", "; + } else { + out << " "; + } + out << "--" << opt.long_opt; + i += 8 + opt.long_opt.size(); + } else if (opt.short_opt != '\0') { + out << " -" << opt.short_opt; + i += 4; + } else { + continue; + } + if (!opt.argument.empty()) { + out << '=' << opt.argument; + i += 1 + opt.argument.size(); + } + pad(out, need - i); + if (opt.help.size() < right) { + out << opt.help << '\n'; + } else { + i = right; + while (i > 0 && !is_separator(opt.help, i)) --i; + if (i == 0) i = right; + out << opt.help.substr(0, i) << '\n'; + while (true) { + while (i < opt.help.size() && is_space(opt.help, i)) ++i; + if (i == opt.help.size()) break; + size_t j = right - 2; + pad(out, width - j); + if (i + j >= opt.help.size()) { + out << opt.help.substr(i) << '\n'; + break; + } + while (j > 0 && !is_separator(opt.help, i + j)) --j; + if (j == 0) j = right - 2; + out << opt.help.substr(i, j) << '\n'; + i += j; + } + } + } + } + +private: + struct Option { + char const short_opt; + std::string const long_opt; + std::string const argument; + std::string const help; + + std::vector<std::string> values; + + Option(char short_opt, std::string const& long_opt, + std::string const& argument, std::string const& help) + : short_opt(short_opt), long_opt(long_opt), argument(argument), + help(help) { + } + }; + bool good_; + std::unordered_map<char, size_t> short_opts_; + std::unordered_map<std::string, size_t> long_opts_; + std::vector<Option> opts_; + std::vector<std::string> args_; + + void reset() { + good_ = true; + args_.clear(); + for (auto& opt : opts_) { + opt.values.clear(); + } + } + + static void pad(std::ostream& out, size_t count) { + while (count > 4) { + out << " "; + count -= 4; + } + while (count) { + out << ' '; + --count; + } + } +}; + +} // namespace + +// static +Args* Args::create() { + return new ArgsImpl(); +} + diff --git a/src/args.hh b/src/args.hh new file mode 100644 index 0000000..7f6d94b --- /dev/null +++ b/src/args.hh @@ -0,0 +1,64 @@ +#ifndef ARGS_HH +#define ARGS_HH + +#include <iostream> +#include <string> +#include <vector> + +class Args { +public: + virtual ~Args() {} + + static Args* create(); + + virtual void add(char short_opt, std::string const& long_opt, + std::string const& argument, std::string const& help) = 0; + void add(char short_opt, std::string const& long_opt, + std::string const& help) { + add(short_opt, long_opt, "", help); + } + void add(std::string const& long_opt, std::string const& help) { + add('\0', long_opt, "", help); + } + void add(std::string const& long_opt, std::string const& argument, + std::string const& help) { + add('\0', long_opt, argument, help); + } + + bool run(int argc, char** argv) { + return run(argc, argv, std::cerr); + } + virtual bool run(int argc, char** argv, std::ostream& out) = 0; + bool run(std::string const& prg, int argc, char** argv) { + return run(prg, argc, argv, std::cerr); + } + virtual bool run( + std::string const& prg, int argc, char** argv, std::ostream& out) = 0; + virtual bool good() const = 0; + + virtual bool is_set(char short_opt) const = 0; + virtual bool is_set(std::string const& long_opt) const = 0; + virtual char const* arg(char short_opt, char const* fallback) const = 0; + virtual char const* arg(std::string const& long_opt, + char const* fallback) const = 0; + virtual size_t count(char short_opt) const = 0; + virtual size_t count(std::string const& long_opt) const = 0; + virtual bool args(char short_opt, std::vector<std::string>* out) const = 0; + virtual bool args(std::string const& long_opt, + std::vector<std::string>* out) const = 0; + + virtual std::vector<std::string> const& arguments() const = 0; + + void print_help() const { + print_help(std::cout); + } + virtual void print_help(std::ostream& out) const = 0; + virtual void print_help(std::ostream& out, size_t width) const = 0; + +protected: + Args() {} + Args(Args const&) = delete; + Args& operator=(Args const&) = delete; +}; + +#endif // ARGS_HH diff --git a/src/clock.cc b/src/clock.cc new file mode 100644 index 0000000..c6b5cc8 --- /dev/null +++ b/src/clock.cc @@ -0,0 +1,24 @@ +#include "common.hh" + +#include <chrono> +#include <thread> + +#include "clock.hh" + +namespace clk { + +double system() { + return std::chrono::duration_cast<std::chrono::duration<double>>( + std::chrono::system_clock::now().time_since_epoch()).count(); +} + +double steady() { + return std::chrono::duration_cast<std::chrono::duration<double>>( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +void sleep(double dur) { + std::this_thread::sleep_for(std::chrono::duration<double>(dur)); +} + +} // namespace clock diff --git a/src/clock.hh b/src/clock.hh new file mode 100644 index 0000000..48f20bd --- /dev/null +++ b/src/clock.hh @@ -0,0 +1,13 @@ +#ifndef CLOCK_HH +#define CLOCK_HH + +namespace clk { + +double system(); +double steady(); + +void sleep(double dur); + +} // namespace clk + +#endif // CLOCK_HH diff --git a/src/common.hh b/src/common.hh new file mode 100644 index 0000000..14d9a2a --- /dev/null +++ b/src/common.hh @@ -0,0 +1,10 @@ +#ifndef COMMON_HH +#define COMMON_HH + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> + +#endif // COMMON_HH diff --git a/src/fake_monitor.cc b/src/fake_monitor.cc new file mode 100644 index 0000000..ebdc79b --- /dev/null +++ b/src/fake_monitor.cc @@ -0,0 +1,191 @@ +#include "common.hh" + +#include <vector> +#include <unordered_map> + +#include "fake_monitor.hh" +#include "looper.hh" + +namespace { + +class FakeMonitorImpl : public Monitor { +public: + explicit FakeMonitorImpl(std::shared_ptr<Looper> const& looper) + : looper_(looper), timer_(0), host_id_(0), job_id_(0), max_jobs_(0) { + } + + ~FakeMonitorImpl() override { + disconnect(); + } + + void connect(std::string const&, std::string const&, + uint16_t) override { + disconnect(); + + timer_ = looper_->schedule( + (rand() % 500) / 1000.0, + std::bind(&FakeMonitorImpl::fake_connect, this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void disconnect() override { + if (timer_) { + looper_->cancel(timer_); + timer_ = 0; + } + machines_.clear(); + for (auto* observer : observers_) { + observer->state(this, SEARCHING); + } + } + + size_t machines() const override { + return machines_.size(); + } + + uint32_t id(size_t index) const override { + auto it = machines_.begin(); + while (index--) ++it; + if (it == machines_.end()) { + assert(false); + return static_cast<uint32_t>(-1); + } + return it->first; + } + + Machine machine_at(size_t index) const override { + auto it = machines_.begin(); + while (index--) ++it; + if (it == machines_.end()) { + assert(false); + return Machine(); + } + return it->second.data; + } + + Machine machine(uint32_t id) const override { + auto it = machines_.find(id); + if (it == machines_.end()) { + assert(false); + return Machine(); + } + return it->second.data; + } + + void add_observer(Observer* observer) override { + assert(observer); + observers_.push_back(observer); + } + +private: + struct Job { + uint32_t const host; + uint32_t const worker; + + Job(uint32_t host, uint32_t worker) + : host(host), worker(worker) { + } + }; + struct Entry { + Machine data; + unsigned active; + }; + + void fake_connect(Looper*, uint32_t) { + timer_ = 0; + + for (auto* observer : observers_) { + observer->state(this, CONNECTED); + } + + add_machine("alice", 4); + add_machine("bob", 10); + add_machine("DeuX", 22); + + schedule_jobs(); + } + + void add_machine(std::string const& name, unsigned max_jobs) { + auto id = ++host_id_; + auto& entry = machines_[id]; + entry.data.name = name; + entry.data.max_jobs = max_jobs; + entry.active = 0; + max_jobs_ += max_jobs; + + for (auto* observer : observers_) { + observer->added_machine(this, id); + } + } + + void schedule_jobs() { + assert(timer_ == 0); + timer_ = looper_->schedule( + (rand() % 2000) / 1000.0, + std::bind(&FakeMonitorImpl::fiddle_jobs, this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void fiddle_jobs(Looper*, uint32_t) { + timer_ = 0; + + auto remove = !jobs_.empty() ? rand() % jobs_.size() : 0; + while (remove--) { + auto it = jobs_.begin(); + auto job = it->second; + jobs_.erase(it); + machines_[job.worker].active--; + for (auto* observer : observers_) { + observer->removed_job(this, job.host, job.worker); + } + } + + auto avail = (max_jobs_ * 7) / 8 - jobs_.size(); + auto add = avail ? rand() % avail : 0; + while (add--) { + auto id = ++job_id_; + while (jobs_.count(id)) { + id = ++job_id_; + } + auto host = rand() % machines_.size(); + auto target = rand() % machines_.size(); + do { + auto tgtid = this->id(target); + auto& entry = machines_[tgtid]; + if (entry.active < entry.data.max_jobs) { + entry.active++; + add_job(id, host != target ? this->id(host) : tgtid, tgtid); + break; + } + target = rand() % machines_.size(); + } while (true); + } + + schedule_jobs(); + } + + void add_job(uint32_t job_id, uint32_t host, uint32_t worker) { + jobs_.insert(std::make_pair(job_id, Job(host, worker))); + for (auto* observer : observers_) { + observer->added_job(this, host, worker); + } + } + + std::shared_ptr<Looper> looper_; + uint32_t timer_; + uint32_t host_id_; + uint32_t job_id_; + size_t max_jobs_; + std::unordered_map<uint32_t, Entry> machines_; + std::vector<Observer*> observers_; + std::unordered_map<uint32_t, Job> jobs_; +}; + +} // namespace + +// static +Monitor* FakeMonitor::create(std::shared_ptr<Looper> const& looper) { + return new FakeMonitorImpl(looper); +} diff --git a/src/fake_monitor.hh b/src/fake_monitor.hh new file mode 100644 index 0000000..836e082 --- /dev/null +++ b/src/fake_monitor.hh @@ -0,0 +1,11 @@ +#ifndef FAKE_MONITOR_HH +#define FAKE_MONITOR_HH + +#include "monitor.hh" + +class FakeMonitor : public Monitor { +public: + static Monitor* create(std::shared_ptr<Looper> const& looper); +}; + +#endif // FAKE_MONITOR_HH diff --git a/src/io.cc b/src/io.cc new file mode 100644 index 0000000..bc70eee --- /dev/null +++ b/src/io.cc @@ -0,0 +1,167 @@ +#include "common.hh" + +#include <errno.h> +#include <unistd.h> + +#include "io.hh" + +namespace { + +bool do_read(int fd, void* dst, size_t max, size_t* got) { + auto d = reinterpret_cast<char*>(dst); + size_t pos = 0; + while (true) { + auto ret = ::read(fd, d + pos, max - pos); + if (ret < 0) { + if (errno == EINTR) continue; + if (got && (errno == EAGAIN || errno == EWOULDBLOCK)) { + *got = pos; + return true; + } + return false; + } + if (ret == 0) { + if (got) { + *got = pos; + return true; + } + return false; + } + pos += ret; + if (got) { + *got = pos; + return true; + } + if (pos == max) { + return true; + } + } +} + +bool do_write(int fd, void const* src, size_t size, size_t* wrote) { + auto s = reinterpret_cast<char const*>(src); + size_t pos = 0; + while (true) { + auto ret = ::write(fd, s + pos, size - pos); + if (ret < 0) { + if (errno == EINTR) continue; + if (wrote && (errno == EAGAIN || errno == EWOULDBLOCK)) { + *wrote = pos; + return true; + } + return false; + } + if (ret == 0) { + if (wrote) { + *wrote = pos; + return true; + } + return false; + } + pos += ret; + if (pos == size) { + if (wrote) *wrote = pos; + return true; + } + } +} + +} // namespace + +namespace io { + +pipe::pipe() { + fd_[0] = fd_[1] = -1; +} + +pipe::pipe(pipe&& pipe) { + fd_[0] = pipe.fd_[0]; + fd_[1] = pipe.fd_[1]; + pipe.fd_[0] = -1; + pipe.fd_[1] = -1; +} + +pipe::~pipe() { + reset(); +} + +bool pipe::open() { + reset(); + if (::pipe(fd_) == 0) return true; + fd_[0] = fd_[1] = -1; + return false; +} + +void pipe::reset() { + if (fd_[0] == -1) return; + ::close(fd_[0]); + ::close(fd_[1]); + fd_[0] = fd_[1] = -1; +} + +void pipe::swap(pipe& pipe) { + auto x = pipe.fd_[0]; + pipe.fd_[0] = fd_[0]; + fd_[0] = x; + x = pipe.fd_[1]; + pipe.fd_[1] = fd_[1]; + fd_[1] = x; +} + +unique_fd::unique_fd() + : fd_(-1) { +} + +unique_fd::unique_fd(int fd) + : fd_(fd) { +} + +unique_fd::unique_fd(unique_fd&& fd) + : fd_(fd.release()) { +} + +unique_fd::~unique_fd() { + reset(); +} + +void unique_fd::reset() { + if (fd_ == -1) return; + ::close(fd_); + fd_ = -1; +} + +void unique_fd::reset(int fd) { + if (fd_ == fd) return; + reset(); + fd_ = fd; +} + +void unique_fd::swap(unique_fd& fd) { + auto x = fd.fd_; + fd.fd_ = fd_; + fd_ = x; +} + +int unique_fd::release() { + auto ret = fd_; + fd_ = -1; + return ret; +} + +bool read(pipe const& pipe, void* dst, size_t max, size_t* got) { + return do_read(pipe.read(), dst, max, got); +} + +bool write(pipe const& pipe, void const* src, size_t size, size_t* wrote) { + return do_write(pipe.write(), src, size, wrote); +} + +bool read(unique_fd const& fd, void* dst, size_t max, size_t* got) { + return do_read(fd.get(), dst, max, got); +} + +bool write(unique_fd const& fd, void const* src, size_t size, size_t* wrote) { + return do_write(fd.get(), src, size, wrote); +} + +} // namespace io diff --git a/src/io.hh b/src/io.hh new file mode 100644 index 0000000..abd91fa --- /dev/null +++ b/src/io.hh @@ -0,0 +1,87 @@ +#ifndef IO_HH +#define IO_HH + +#include <stddef.h> + +namespace io { + +class pipe { +public: + pipe(); + pipe(pipe&& pipe); + ~pipe(); + + pipe& operator=(pipe&& pipe) { + reset(); + swap(pipe); + return *this; + } + + bool open(); + explicit operator bool() const { + return read() != -1; + } + + void reset(); + + void swap(pipe& pipe); + + int read() const { + return fd_[0]; + } + int write() const { + return fd_[1]; + } + +private: + pipe(pipe const&) = delete; + pipe& operator=(pipe const&) = delete; + + int fd_[2]; +}; + +class unique_fd { +public: + unique_fd(); + explicit unique_fd(int fd); + unique_fd(unique_fd&& fd); + ~unique_fd(); + + unique_fd& operator=(unique_fd&& fd) { + reset(); + swap(fd); + return *this; + } + + int get() const { + return fd_; + } + + explicit operator bool() const { + return get() != -1; + } + + void reset(); + void reset(int fd); + + void swap(unique_fd& fd); + + int release(); + +private: + unique_fd(unique_fd const& fd) = delete; + unique_fd& operator=(unique_fd const& fd) = delete; + + int fd_; +}; + +bool read(pipe const& pipe, void* dst, size_t max, size_t* got = nullptr); +bool write(pipe const& pipe, void const* src, size_t size, + size_t* wrote = nullptr); +bool read(unique_fd const& fd, void* dst, size_t max, size_t* got = nullptr); +bool write(unique_fd const& fd, void const* src, size_t size, + size_t* wrote = nullptr); + +} // namespace + +#endif // IO_HH diff --git a/src/looper.cc b/src/looper.cc new file mode 100644 index 0000000..0166c12 --- /dev/null +++ b/src/looper.cc @@ -0,0 +1,10 @@ +#include "common.hh" + +#include "looper.hh" + +// static +uint8_t const Looper::EV_READ = 1 << 0; +// static +uint8_t const Looper::EV_WRITE = 1 << 2; +// static +uint8_t const Looper::EV_ERR = 1 << 3; diff --git a/src/looper.hh b/src/looper.hh new file mode 100644 index 0000000..4dfe56f --- /dev/null +++ b/src/looper.hh @@ -0,0 +1,31 @@ +#ifndef LOOPER_HH +#define LOOPER_HH + +#include <functional> + +class Looper { +public: + virtual ~Looper() {} + + static uint8_t const EV_READ; + static uint8_t const EV_WRITE; + static uint8_t const EV_ERR; + + virtual void add(int fd, uint8_t events, + std::function<void(Looper*, int, uint8_t)> + const& callback) = 0; + virtual void modify(int fd, uint8_t events) = 0; + virtual void remove(int fd) = 0; + + // Returned id is never 0 + virtual uint32_t schedule( + double delay, std::function<void(Looper*, uint32_t)> const& callback) = 0; + virtual void cancel(uint32_t id) = 0; + +protected: + Looper() {} + Looper(Looper const&) = delete; + Looper& operator=(Looper const&) = delete; +}; + +#endif // LOOPER_HH diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..3cdb618 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,872 @@ +#include "common.hh" + +#include <algorithm> +#include <cairo-xcb.h> +#include <iostream> +#include <math.h> +#include <mutex> +#include <pango/pangocairo.h> +#include <string.h> +#include <thread> +#include <xcb/xcb_event.h> +#include <xcb/xcb_icccm.h> +#include <xcb/xcb_keysyms.h> + +#include "args.hh" +#include "fake_monitor.hh" +#include "io.hh" +#include "monitor.hh" +#include "poll_looper.hh" +#include "x.hh" + +namespace { + +struct CairoSurfaceDelete { + void operator()(cairo_surface_t* surface) const { + cairo_surface_destroy(surface); + } +}; + +struct CairoDelete { + void operator()(cairo_t* cairo) const { + cairo_destroy(cairo); + } +}; + +struct GObjectDelete { + void operator()(gpointer ptr) const { + g_object_unref(ptr); + } +}; + +struct PangoFontMetricsDelete { + void operator()(PangoFontMetrics* ptr) const { + pango_font_metrics_unref(ptr); + } +}; + +struct CairoPatternDelete { + void operator()(cairo_pattern_t* pattern) const { + cairo_pattern_destroy(pattern); + } +}; + +struct CairoPathDelete { + void operator()(cairo_path_t* path) const { + cairo_path_destroy(path); + } +}; + +struct MwmHints { + uint32_t flags; + uint32_t functions; + uint32_t decorations; + int32_t input_mode; + uint32_t status; +}; + +class MonMon : virtual Monitor::Observer { +public: + explicit MonMon(std::shared_ptr<PollLooper> const& looper) + : looper_(looper), connected_(false), depth_(0), black_pixel_(0), + screen_(nullptr), max_jobs_(0), jobs_(0), requests_(0), + x_(0), y_(0), w_(0), h_(0), + rootpmap_(XCB_ATOM_NONE), desktop_window_(XCB_NONE), + desktop_pixmap_(XCB_NONE) { + pipe_.open(); + looper_->add(pipe_.read(), Looper::EV_READ, + std::bind(&MonMon::pipe, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + } + + ~MonMon() { + unset_desktop_window(); + close_pipe(); + wnd_.reset(); + } + + void init(x::shared_connection const& conn, xcb_screen_t const* screen, + x::Format format, std::shared_ptr<x::Atoms> const& atoms, + std::shared_ptr<x::Ewmh> const& ewmh, + uint16_t width, uint16_t height, bool normal, bool black) { + screen_ = screen; + wnd_.reset(conn); + pixmap_.reset(conn); + gcontext_.reset(conn); + + uint32_t values[2]; + values[0] = screen->black_pixel; + values[1] = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_EXPOSURE + | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + xcb_create_window(wnd_.conn(), format.depth, + wnd_.get(), screen->root, + 0, 0, width, height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + format.visual->visual_id, + XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, + values); + if (!normal) { + if (atoms->get("_MOTIF_WM_INFO", false) != XCB_ATOM_NONE) { + auto atom = atoms->get("_MOTIF_WM_HINTS"); + MwmHints hints; + hints.flags = 1 << 1; // MWM_HINTS_DECORATIONS + hints.decorations = 0; + xcb_change_property(wnd_.conn(), XCB_PROP_MODE_REPLACE, wnd_.get(), + atom, atom, 32, sizeof(hints) / 4, &hints); + } else { +#ifndef NDEBUG + // There is no way to know how the window manager will implement + // utility window type, some might show them without titlebar ... + std::cerr << "Warning: No motif support detected, falling back to " + << "NETWM utility window type." << std::endl; +#endif + auto wm = ewmh->conn(); + if (wm && ewmh->supported(wm->_NET_WM_WINDOW_TYPE_UTILITY)) { + xcb_atom_t values[1]; + values[0] = wm->_NET_WM_WINDOW_TYPE_UTILITY; + xcb_ewmh_set_wm_window_type(wm, wnd_.get(), 1, values); + } else { + // TODO: Fallback to override-redirect? + } + } + } + if (!black) { + rootpmap_ = atoms->get("_XROOTPMAP_ID", false); + if (rootpmap_ == XCB_ATOM_NONE) { + std::cerr << "Warning: No support for Eterm style background pixmap" + << std::endl; + } + } + xcb_create_gc(gcontext_.conn(), gcontext_.get(), wnd_.get(), 0, nullptr); + auto atom = atoms->get("WM_DELETE_WINDOW"); + xcb_icccm_set_wm_protocols(wnd_.conn(), wnd_.get(), + atoms->get("WM_PROTOCOLS"), + 1, &atom); + xcb_create_pixmap(pixmap_.conn(), format.depth, pixmap_.get(), wnd_.get(), + width, height); + if (format.render) { + surface_.reset( + cairo_xcb_surface_create_with_xrender_format( + pixmap_.conn(), + const_cast<xcb_screen_t*>(screen), + pixmap_.get(), + format.render, + width, height)); + if (cairo_surface_status(surface_.get()) != CAIRO_STATUS_SUCCESS) { + surface_.reset(); + } + } + if (!surface_) { + surface_.reset( + cairo_xcb_surface_create(pixmap_.conn(), pixmap_.get(), + format.visual, width, height)); + } + cairo_.reset(cairo_create(surface_.get())); + layout_.reset(pango_cairo_create_layout(cairo_.get())); + pango_layout_set_ellipsize(layout_.get(), PANGO_ELLIPSIZE_MIDDLE); + pango_layout_set_alignment(layout_.get(), PANGO_ALIGN_CENTER); + pango_layout_set_height(layout_.get(), 0); + auto context = pango_layout_get_context(layout_.get()); + auto metrics = std::unique_ptr<PangoFontMetrics, PangoFontMetricsDelete>( + pango_context_get_metrics(context, nullptr, nullptr)); + box_height_ = static_cast<double>( + pango_font_metrics_get_ascent(metrics.get()) + + pango_font_metrics_get_descent(metrics.get())) / PANGO_SCALE; + x_ = 0; + y_ = 0; + w_ = width; + h_ = height; + depth_ = format.depth; + black_pixel_ = screen->black_pixel; + update_desktop_window(); + internal_draw(); + xcb_map_window(wnd_.conn(), wnd_.get()); + xcb_flush(wnd_.conn()); + } + + void connect(Args const* args) { +#if FAKE_MONITOR + monitor_.reset(FakeMonitor::create(looper_)); +#else + monitor_.reset(Monitor::create(looper_)); +#endif + monitor_->add_observer(this); + monitor_->connect(args->arg("network", ""), + args->arg("scheduler", "")); + } + + void quit_from_xcb() { + io::write(pipe_, "q", 1); + } + + /* + void quit_from_main(std::shared_ptr<x::Atoms> const& atoms) { + // Wake up the event thread by sending an event to our window + xcb_client_message_event_t event; + memset(&event, 0, sizeof(event)); + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.sequence = 0; + event.window = wnd_.get(); + event.type = atoms->get("__MONMON_QUIT"); + + xcb_send_event(wnd_.conn(), false, wnd_.get(), XCB_EVENT_MASK_NO_EVENT, + reinterpret_cast<char*>(&event)); + xcb_flush(wnd_.conn()); + } + */ + + void expose(int16_t x, int16_t y, uint16_t w, uint16_t h) { + std::lock_guard<std::mutex> lock(mutex_); + xcb_copy_area(wnd_.conn(), pixmap_.get(), wnd_.get(), gcontext_.get(), + x, y, x, y, w, h); + } + + bool match(xcb_window_t wnd) const { + return wnd == wnd_.get(); + } + + void configure(int16_t x, int16_t y, uint16_t w, uint16_t h) { + bool draw = false; + if (rootpmap_ && (x != x_ || y != y_)) { + x_ = x; + y_ = y; + if (desktop_surface_) { + draw = true; + io::write(pipe_, "d", 1); + } + } + if (w == w_ && h == h_) return; + { + std::lock_guard<std::mutex> lock(mutex_); + x::unique_pixmap tmp(pixmap_.shared_conn()); + x::unique_gcontext gc(tmp.shared_conn()); + cairo_surface_flush(surface_.get()); + xcb_create_pixmap(tmp.conn(), depth_, tmp.get(), wnd_.get(), w, h); + uint32_t value[1]; + value[0] = black_pixel_; + xcb_create_gc(gc.conn(), gc.get(), tmp.get(), XCB_GC_FOREGROUND, value); + uint32_t count = 0; + xcb_rectangle_t rect[2]; + uint16_t copy_width, copy_height; + if (w > w_) { + copy_width = w_; + rect[count].x = w_; + rect[count].width = w - w_; + rect[count].y = 0; + rect[count].height = h_; + ++count; + } else { + copy_width = w; + } + if (h > h_) { + copy_height = h_; + rect[count].x = 0; + rect[count].width = w; + rect[count].y = h_; + rect[count].height = h - h_; + ++count; + } else { + copy_height = h; + } + if (count) { + xcb_poly_fill_rectangle(tmp.conn(), tmp.get(), gc.get(), + count, rect); + } + xcb_copy_area(tmp.conn(), pixmap_.get(), tmp.get(), gc.get(), 0, 0, 0, 0, + copy_width, copy_height); + cairo_xcb_surface_set_drawable(surface_.get(), tmp.get(), w, h); + if (w_ != w) { + job_pattern_.reset(); + w_ = w; + } + h_ = h; + pixmap_ = std::move(tmp); + xcb_copy_area(wnd_.conn(), pixmap_.get(), wnd_.get(), gcontext_.get(), + 0, 0, 0, 0, w, h); + } + if (!draw) io::write(pipe_, "d", 1); + } + + void update_desktop_window() { + if (rootpmap_ == XCB_ATOM_NONE) return; + + xcb_window_t desktop_window = XCB_NONE; + + auto last = wnd_.get(); + while (true) { + auto cookie = xcb_query_tree(wnd_.conn(), last); + auto reply = xcb_query_tree_reply(wnd_.conn(), cookie, nullptr); + if (!reply) break; + auto cookie2 = xcb_get_property(wnd_.conn(), 0, reply->parent, rootpmap_, + XCB_GET_PROPERTY_TYPE_ANY, 0, 1); + auto reply2 = xcb_get_property_reply(wnd_.conn(), cookie2, nullptr); + if (reply2) { + if (reply2->type != 0 && reply2->format != 0 && reply2->length != 0) { + desktop_window = reply->parent; + free(reply2); + free(reply); + break; + } + free(reply2); + } + if (reply->parent == reply->root) { + free(reply); + break; + } + last = reply->parent; + free(reply); + } + + if (desktop_window == desktop_window_) return; + unset_desktop_window(); + if (desktop_window == XCB_NONE) return; + + uint32_t values[1]; + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(wnd_.conn(), desktop_window, + XCB_CW_EVENT_MASK, + values); + desktop_window_ = desktop_window; + update_desktop_pixmap(); + } + + void update_desktop_window(xcb_window_t window, xcb_atom_t property) { + if (window != desktop_window_) return; + if (property != rootpmap_) return; + + update_desktop_pixmap(); + } + +private: + struct Machine { + uint32_t id; + Monitor::Machine data; + unsigned jobs; + unsigned requests; + + explicit Machine(uint32_t id) + : id(id), jobs(0), requests(0) { + } + + bool operator<(Machine const& machine) const { + if (data.name < machine.data.name) return true; + if (data.name > machine.data.name) return false; + return id < machine.id; + } + }; + + void unset_desktop_window() { + if (desktop_window_ == XCB_NONE) return; + uint32_t values[1]; + values[0] = 0; + xcb_change_window_attributes(wnd_.conn(), desktop_window_, + XCB_CW_EVENT_MASK, + values); + desktop_window_ = XCB_NONE; + update_desktop_pixmap(); + } + + void update_desktop_pixmap() { + xcb_pixmap_t pixmap = XCB_NONE; + + if (desktop_window_ != XCB_NONE) { + auto cookie = xcb_get_property(wnd_.conn(), 0, desktop_window_, rootpmap_, + XCB_GET_PROPERTY_TYPE_ANY, 0, 1); + auto reply = xcb_get_property_reply(wnd_.conn(), cookie, nullptr); + if (reply) { + if (reply->type == XCB_ATOM_PIXMAP) { + pixmap = *reinterpret_cast<xcb_pixmap_t*>( + xcb_get_property_value(reply)); + } + free(reply); + } + } + + if (pixmap == desktop_pixmap_) return; + + auto geometry_cookie = xcb_get_geometry(wnd_.conn(), pixmap); + auto attr_cookie = xcb_get_window_attributes(wnd_.conn(), desktop_window_); + + auto geometry_reply = xcb_get_geometry_reply(wnd_.conn(), geometry_cookie, + nullptr); + uint16_t w, h; + uint8_t depth; + if (geometry_reply) { + depth = geometry_reply->depth; + w = geometry_reply->width; + h = geometry_reply->height; + free(geometry_reply); + } else { + pixmap = XCB_NONE; + } + + auto attr_reply = xcb_get_window_attributes_reply(wnd_.conn(), attr_cookie, + nullptr); + if (attr_reply) { + auto iter = xcb_screen_allowed_depths_iterator(screen_); + auto count = xcb_screen_allowed_depths_length(screen_); + while (count--) { + if (iter.data->depth == depth) { + auto visuals = xcb_depth_visuals(iter.data); + auto visual_count = xcb_depth_visuals_length(iter.data); + while (visual_count--) { + if (visuals->visual_id == attr_reply->visual) { + if (pixmap != XCB_NONE) { + desktop_surface_.reset(cairo_xcb_surface_create( + wnd_.conn(), pixmap, + visuals, w, h)); + } + break; + } + ++visuals; + } + } + xcb_depth_next(&iter); + } + free(attr_reply); + } else { + pixmap = XCB_NONE; + } + + desktop_pixmap_ = pixmap; + if (desktop_pixmap_ == XCB_NONE) desktop_surface_.reset(); + + io::write(pipe_, "d", 1); + } + + void close_pipe() { + if (!pipe_) return; + looper_->remove(pipe_.read()); + pipe_.reset(); + } + + void pipe(Looper*, int, uint8_t event) { + if (event & Looper::EV_READ) { + char tmp; + if (io::read(pipe_, &tmp, 1)) { + if (tmp == 'd') { + draw(); + return; + } + } + } + + monitor_->disconnect(); + looper_->exit_when_empty(); + close_pipe(); + } + + void state(Monitor*, Monitor::State state) override { + switch (state) { + case Monitor::SEARCHING: + connected_ = false; + machines_.clear(); + max_jobs_ = 0; + jobs_ = 0; + requests_ = 0; + if (!wnd_) return; + draw(); + break; + case Monitor::CONNECTED: + connected_ = true; + draw(); + break; + } + } + + void update(Machine& machine) { + auto old = machine.data.max_jobs; + machine.data = monitor_->machine(machine.id); + if (old < machine.data.max_jobs) { + max_jobs_ += machine.data.max_jobs - old; + } else if (old > machine.data.max_jobs) { + max_jobs_ -= old - machine.data.max_jobs; + } + } + + void draw() { + internal_draw(); + xcb_flush(wnd_.conn()); + } + + static void rounded_path(cairo_t* cr, double x, double y, + double width, double height) { + auto radius = height / 10.0; + auto degrees = M_PI / 180.0; + + cairo_new_sub_path(cr); + cairo_arc(cr, x + width - radius, y + radius, radius, + -90 * degrees, 0 * degrees); + cairo_arc(cr, x + width - radius, y + height - radius, radius, + 0 * degrees, 90 * degrees); + cairo_arc(cr, x + radius, y + height - radius, radius, + 90 * degrees, 180 * degrees); + cairo_arc(cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees); + cairo_close_path(cr); + } + + void internal_draw() { + std::lock_guard<std::mutex> lock(mutex_); + if (!desktop_surface_) { + cairo_set_source_rgb(cairo_.get(), 0, 0, 0); + } else { + cairo_set_source_surface(cairo_.get(), desktop_surface_.get(), + -x_, -y_); + } + cairo_rectangle(cairo_.get(), 0, 0, w_, h_); + cairo_fill(cairo_.get()); + auto pad_x = std::min(9.0, w_ / 20.0), pad_y = pad_x; + auto margin_x = std::min(7.5, w_ / 20.0), margin_y = margin_x; + auto y = pad_y; + auto box_width = w_ - pad_x * 2; + auto box_height = box_height_ + margin_y * 2; + if (!job_pattern_) { + job_pattern_.reset(cairo_pattern_create_linear(0.0, 0.0, box_width, 0.0)); + cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.000000, + 0.000000, 0.000000, 0.000000); + cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.594324, + 0.729412, 0.000000, 0.000000); + cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.809683, + 1.000000, 0.545098, 0.196078); + cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 0.899833, + 0.972549, 0.937255, 0.074510); + cairo_pattern_add_color_stop_rgb(job_pattern_.get(), 1.000000, + 0.976471, 0.968627, 0.831373); + } + pango_layout_set_width(layout_.get(), + (box_width - margin_x * 2) * PANGO_SCALE); + for (auto const& machine : machines_) { + pango_layout_set_text(layout_.get(), machine.data.name.data(), + machine.data.name.size()); + rounded_path(cairo_.get(), pad_x, y, box_width, box_height); + cairo_set_source_rgba(cairo_.get(), 0.1, 0.1, 0.1, 0.7); + + if (machine.jobs > 0) { + cairo_fill_preserve(cairo_.get()); + auto old = std::unique_ptr<cairo_path_t, CairoPathDelete>(cairo_copy_path(cairo_.get())); + cairo_new_path(cairo_.get()); + cairo_rectangle(cairo_.get(), pad_x, y, + (machine.jobs * box_width) / machine.data.max_jobs, + box_height); + cairo_clip(cairo_.get()); + cairo_append_path(cairo_.get(), old.get()); + cairo_set_source(cairo_.get(), job_pattern_.get()); + cairo_fill(cairo_.get()); + cairo_reset_clip(cairo_.get()); + } else { + cairo_fill(cairo_.get()); + } + if (machine.requests > 0) { + cairo_set_line_width(cairo_.get(), 1.5); + cairo_new_path(cairo_.get()); + cairo_move_to(cairo_.get(), pad_x + box_width, y); + cairo_rel_line_to(cairo_.get(), + -(machine.requests * box_width) / requests_, 0); + cairo_set_source_rgb(cairo_.get(), 0, 0.6, 0); + cairo_stroke(cairo_.get()); + } + + cairo_move_to(cairo_.get(), pad_x + margin_x, y + margin_y); + pango_cairo_layout_path(cairo_.get(), layout_.get()); + cairo_set_source_rgb(cairo_.get(), 0.0, 0.0, 0.0); + cairo_set_line_cap(cairo_.get(), CAIRO_LINE_CAP_ROUND); + cairo_set_line_width(cairo_.get(), 2.0); + cairo_stroke(cairo_.get()); + cairo_set_source_rgb(cairo_.get(), 1.0, 1.0, 1.0); + cairo_move_to(cairo_.get(), pad_x + margin_x, y + margin_y); + pango_cairo_show_layout(cairo_.get(), layout_.get()); + y += box_height + pad_y * 2; + if (y >= h_) break; + } + cairo_surface_flush(surface_.get()); + xcb_copy_area(wnd_.conn(), pixmap_.get(), wnd_.get(), gcontext_.get(), + 0, 0, 0, 0, w_, h_); + } + + void added_machine(Monitor*, uint32_t id) override { + Machine machine(id); + update(machine); + machines_.insert( + std::lower_bound(machines_.begin(), machines_.end(), machine), + machine); + draw(); + } + + void updated_machine(Monitor*, uint32_t id) override { + for (auto& machine : machines_) { + if (machine.id == id) { + auto old = machine.data.name; + update(machine); + if (machine.data.name != old) { + // TODO: Perhaps remove and insert instead? + std::sort(machines_.begin(), machines_.end()); + } + draw(); + return; + } + } + assert(false); + } + + void removed_machine(Monitor*, uint32_t id) override { + for (auto it = machines_.begin(); it != machines_.end(); ++it) { + if (it->id == id) { + max_jobs_ -= it->data.max_jobs; + requests_ -= it->requests; + jobs_ -= it->jobs; + machines_.erase(it); + draw(); + return; + } + } + assert(false); + } + + void added_job(Monitor*, uint32_t source, uint32_t target) override { + if (source == target) { + for (auto& machine : machines_) { + if (machine.id == source) { + ++machine.jobs; + } + } + } else { + for (auto& machine : machines_) { + if (machine.id == source) { + ++machine.requests; + } + if (machine.id == target) { + ++machine.jobs; + } + } + ++requests_; + } + ++jobs_; + draw(); + } + + void removed_job(Monitor*, uint32_t source, uint32_t target) override { + if (source == target) { + for (auto& machine : machines_) { + if (machine.id == source) { + --machine.jobs; + } + } + } else { + for (auto& machine : machines_) { + if (machine.id == source) { + --machine.requests; + } + if (machine.id == target) { + --machine.jobs; + } + } + --requests_; + } + --jobs_; + draw(); + } + + std::shared_ptr<PollLooper> looper_; + std::unique_ptr<Monitor> monitor_; + bool connected_; + io::pipe pipe_; + std::shared_ptr<x::Atoms> atoms_; + x::unique_window wnd_; + x::unique_gcontext gcontext_; + uint8_t depth_; + uint32_t black_pixel_; + xcb_screen_t const* screen_; + double box_height_; + std::vector<Machine> machines_; + unsigned max_jobs_; + unsigned jobs_; + unsigned requests_; + std::mutex mutex_; + std::unique_ptr<cairo_surface_t, CairoSurfaceDelete> surface_; + std::unique_ptr<cairo_t, CairoDelete> cairo_; + std::unique_ptr<cairo_pattern_t, CairoPatternDelete> job_pattern_; + std::unique_ptr<PangoLayout, GObjectDelete> layout_; + x::unique_pixmap pixmap_; + int16_t x_, y_; + uint16_t w_, h_; + xcb_atom_t rootpmap_; + xcb_window_t desktop_window_; + xcb_pixmap_t desktop_pixmap_; + std::unique_ptr<cairo_surface_t, CairoSurfaceDelete> desktop_surface_; +}; + +struct XcbKeySymbolsDelete { + void operator()(xcb_key_symbols_t* ptr) const { + if (ptr) xcb_key_symbols_free(ptr); + } +}; + +struct XcbGenericEventDelete { + void operator()(xcb_generic_event_t* ptr) const { + free(ptr); + } +}; + +void xcb_event_loop(x::shared_connection const& conn, + std::shared_ptr<x::Atoms> const& atoms, + std::shared_ptr<MonMon> const& monmon) { + std::unique_ptr<xcb_key_symbols_t, XcbKeySymbolsDelete> syms; + syms.reset(xcb_key_symbols_alloc(conn.get())); + std::unique_ptr<xcb_generic_event_t, XcbGenericEventDelete> event; + auto const quit = atoms->get("__MONMON_QUIT"); + auto const wm_protocols = atoms->get("WM_PROTOCOLS"); + auto const wm_delete_window = atoms->get("WM_DELETE_WINDOW"); + while (true) { + event.reset(xcb_wait_for_event(conn.get())); + if (!event) { + std::cerr << "fatal error: " + << xcb_connection_has_error(conn.get()) << std::endl; + monmon->quit_from_xcb(); + return; + } + if (event->response_type == 0) { + auto e = reinterpret_cast<xcb_generic_error_t*>(event.get()); +#ifndef NDEBUG + std::cerr << "error: " << xcb_event_get_error_label(e->error_code) + << std::endl; +#endif + continue; + } + switch (XCB_EVENT_RESPONSE_TYPE(event)) { + case XCB_EXPOSE: { + auto e = reinterpret_cast<xcb_expose_event_t*>(event.get()); + if (monmon->match(e->window)) { + monmon->expose(e->x, e->y, e->width, e->height); + } + if (e->count != 0) continue; // Only flush for e->count == 0 + break; + } + case XCB_KEY_PRESS: { + auto e = reinterpret_cast<xcb_key_press_event_t*>(event.get()); + auto sym = xcb_key_press_lookup_keysym(syms.get(), e, 0); + switch (sym) { + case 'Q': + case 'q': + case 0xff1b: { // escape + if (monmon->match(e->event)) { + monmon->quit_from_xcb(); + return; + } + break; + } + } + break; + } + case XCB_DESTROY_NOTIFY: { + auto e = reinterpret_cast<xcb_destroy_notify_event_t*>(event.get()); + if (monmon->match(e->window)) { + monmon->quit_from_xcb(); + return; + } + break; + } + case XCB_CONFIGURE_NOTIFY: { + auto e = reinterpret_cast<xcb_configure_notify_event_t*>(event.get()); + if (monmon->match(e->window)) { + monmon->configure(e->x, e->y, e->width, e->height); + } + break; + } + case XCB_CLIENT_MESSAGE: { + auto e = reinterpret_cast<xcb_client_message_event_t*>(event.get()); + if (e->format == 32 + && e->type == wm_protocols + && e->data.data32[0] == wm_delete_window) { + if (monmon->match(e->window)) { + monmon->quit_from_xcb(); + return; + } + } else if (e->format == 32 && e->type == quit) { + // Do not call quit_from_xcb() here, only reason to end up + // here is if main already called quit_from_main() + return; + } + break; + } + case XCB_MAPPING_NOTIFY: { + auto e = reinterpret_cast<xcb_mapping_notify_event_t*>(event.get()); + xcb_refresh_keyboard_mapping(syms.get(), e); + break; + } + case XCB_REPARENT_NOTIFY: { + auto e = reinterpret_cast<xcb_reparent_notify_event_t*>(event.get()); + if (monmon->match(e->window)) { + monmon->update_desktop_window(); + } + break; + } + case XCB_PROPERTY_NOTIFY: { + auto e = reinterpret_cast<xcb_property_notify_event_t*>(event.get()); + monmon->update_desktop_window(e->window, e->atom); + break; + } + } + xcb_flush(conn.get()); + } +} + +} // namespace + +int main(int argc, char** argv) { + std::unique_ptr<Args> args(Args::create()); + args->add("network", "NETNAME", + "monitor NETNAME instead of default ICECREAM"); + args->add("scheduler", "HOST", + "connect to scheduler at HOST instead of broadcasting"); + args->add("display", "HOST:VS", "connect to X server at [HOST]:[VS]"); + args->add("no-render", "do not use render extension even if available"); + args->add("titlebar", "create a normal window instead of the default without" + " titlebar (or other WM addons)"); + args->add("black", "use black background instead of background pixmap"); + args->add("help", "display this text and exit."); + if (!args->run(argc, argv)) { + std::cout << "Try `monmon --help` for usage." << std::endl; + return EXIT_FAILURE; + } + if (args->is_set("help")) { + std::cout << "Usage: `monmon [OPTIONS...]`\n" + << "icecream (icecc) monitor\n" + << "\n"; + args->print_help(std::cout); + return EXIT_FAILURE; + } + int screen_index; + x::shared_connection conn( + xcb_connect(args->arg("display", nullptr), &screen_index)); + if (xcb_connection_has_error(conn.get())) { + std::cerr << "Unable to connect to X server." << std::endl; + return EXIT_FAILURE; + } + x::prefetch_extensions(conn.get()); + auto screen = x::get_screen(conn.get(), screen_index); + auto format = x::get_best_format(conn.get(), screen, + !args->is_set("no-render")); + if (!format.good()) { + std::cerr << "Unable to find a good X visual." << std::endl; + return EXIT_FAILURE; + } + std::shared_ptr<PollLooper> looper(PollLooper::create()); + std::shared_ptr<x::Atoms> atoms(x::Atoms::create(conn)); + atoms->preload("WM_DELETE_WINDOW"); + atoms->preload("WM_PROTOCOLS"); + atoms->preload("__MONMON_QUIT"); + atoms->preload("_MOTIF_WM_INFO", false); + atoms->preload("_MOTIF_WM_HINTS"); + atoms->preload("_XROOTPMAP_ID", false); + std::shared_ptr<x::Ewmh> ewmh(x::Ewmh::create(conn, screen_index)); + auto monmon = std::make_shared<MonMon>(looper); + monmon->init(conn, screen, format, atoms, ewmh, 400, 400, + args->is_set("titlebar"), args->is_set("black")); + monmon->connect(args.get()); + std::thread xcb_thread(xcb_event_loop, conn, atoms, monmon); + conn.reset(); + looper->run(); + xcb_thread.join(); + return EXIT_SUCCESS; +} diff --git a/src/monitor.cc b/src/monitor.cc new file mode 100644 index 0000000..e5a231d --- /dev/null +++ b/src/monitor.cc @@ -0,0 +1,410 @@ +#include "common.hh" + +#include <icecc/comm.h> +#include <iostream> +#include <unordered_map> +#include <vector> + +#include "looper.hh" +#include "monitor.hh" + +namespace { + +class MonitorImpl : public Monitor { +public: + explicit MonitorImpl(std::shared_ptr<Looper> const& looper) + : looper_(looper), discover_timer_(0), connect_(false), discover_fd_(-1), + port_(0) { + } + + ~MonitorImpl() override { + disconnect(); + } + + void connect(std::string const& netname, + std::string const& hostname, + uint16_t port) override { + netname_ = netname; + hostname_ = hostname; + port_ = port; + + if (channel_) { + // TODO(the_jk): Should we reconnect here? + return; + } + close_discover(); + schedule_discover((rand() % 1000) / 1000.0); + } + + void disconnect() override { + close_discover(); + disconnect_channel(); + } + + size_t machines() const override { + return machines_.size(); + } + + uint32_t id(size_t index) const override { + auto it = machines_.begin(); + while (index--) ++it; + if (it == machines_.end()) { + assert(false); + return 0xffffffff; + } + return it->first; + } + + Machine machine_at(size_t index) const override { + auto it = machines_.begin(); + while (index--) ++it; + if (it == machines_.end()) { + assert(false); + return EMPTY; + } + return it->second; + } + + Machine machine(uint32_t id) const override { + auto it = machines_.find(id); + if (it == machines_.end()) { + assert(false); + return EMPTY; + } + return it->second; + } + + void add_observer(Observer* observer) override { + observers_.push_back(observer); + } + +private: + static Machine const EMPTY; + + struct Job { + uint32_t source; + uint32_t target; + }; + + void schedule_discover(double delay) { + if (discover_timer_) return; + discover_timer_ = looper_->schedule(delay, + std::bind(&MonitorImpl::discover, + this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void discover(Looper*, uint32_t timer) { + assert(discover_timer_ == timer); + discover_timer_ = 0; + check_discover(); + } + + void close_discover() { + if (discover_timer_) { + looper_->cancel(discover_timer_); + discover_timer_ = 0; + } + if (!discover_) return; + connect_ = false; + if (discover_fd_ >= 0) { + looper_->remove(discover_fd_); + discover_fd_ = -1; + } + discover_.reset(); + } + + void check_discover() { + if (channel_) return; + + if (!discover_) { + discover_.reset(new DiscoverSched(netname_, 2, hostname_, port_)); + } + + channel_.reset(discover_->try_get_scheduler()); + + if (channel_) { + connected(); + return; + } + + if (discover_->timed_out()) { + close_discover(); + check_discover(); + return; + } + + if (discover_fd_ < 0) { + auto fd = discover_->connect_fd(); + if (!connect_ && fd >= 0) { + discover_fd_ = fd; + connect_ = true; + looper_->add(fd, Looper::EV_WRITE, + std::bind(&MonitorImpl::discover_fd, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + // Use connect() timeout + return; + } + fd = discover_->listen_fd(); + if (fd >= 0) { + discover_fd_ = fd; + connect_ = false; + looper_->add(fd, Looper::EV_READ, + std::bind(&MonitorImpl::discover_fd, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } + } + + schedule_discover(1.0 + (rand() % 1000) / 1000.0); + } + + void discover_fd(Looper*, int fd, uint8_t) { + assert(fd == discover_fd_); + looper_->remove(discover_fd_); + discover_fd_ = -1; + check_discover(); + } + + void connected() { + std::cerr << "connected" << std::endl; + current_netname_ = discover_->schedulerName(); + current_hostname_ = discover_->networkName(); + + channel_->setBulkTransfer(); + + close_discover(); + + looper_->add(channel_->fd, Looper::EV_READ, + std::bind(&MonitorImpl::msg, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + + if (!channel_->send_msg(MonLoginMsg())) { + std::cerr << "login failed" << std::endl; + disconnect_channel(); + schedule_discover(0.0); + return; + } + + for (auto* observer : observers_) { + observer->state(this, CONNECTED); + } + } + + void disconnect_channel() { + if (!channel_) return; + std::cerr << "disconnect" << std::endl; + looper_->remove(channel_->fd); + channel_.reset(); + + for (auto* observer : observers_) { + observer->state(this, SEARCHING); + } + } + + void msg(Looper*, int, uint8_t event) { + if (event & Looper::EV_ERR) { + disconnect(); + schedule_discover(0.0); + return; + } + + while (true) { + if (!channel_->read_a_bit()) { + disconnect(); + schedule_discover(0.0); + return; + } + if (!channel_->has_msg()) break; + std::unique_ptr<Msg> msg(channel_->get_msg()); + if (!msg) { + disconnect(); + schedule_discover(0.0); + return; + } + switch (msg->type) { + case M_END: + disconnect(); + schedule_discover(1.0); + return; + case M_MON_STATS: + handle_stats(static_cast<MonStatsMsg*>(msg.get())); + break; + case M_MON_GET_CS: + handle_job(static_cast<MonGetCSMsg*>(msg.get())); + break; + case M_MON_JOB_BEGIN: + handle_job(static_cast<MonJobBeginMsg*>(msg.get())); + break; + case M_MON_JOB_DONE: + handle_job(static_cast<MonJobDoneMsg*>(msg.get())); + break; + case M_MON_LOCAL_JOB_BEGIN: + handle_local_job(static_cast<MonLocalJobBeginMsg*>(msg.get())); + break; + case M_JOB_LOCAL_DONE: + handle_local_job(static_cast<JobLocalDoneMsg*>(msg.get())); + break; + default: + break; + } + } + } + + void handle_job(MonGetCSMsg const* msg) { + pending_jobs_[msg->job_id] = msg->clientid; + } + + void handle_job(MonJobBeginMsg const* msg) { + auto it = pending_jobs_.find(msg->job_id); + if (it == pending_jobs_.end()) { + assert(false); + return; + } + auto source = it->second; + pending_jobs_.erase(it); + job_begin(msg->job_id, source, msg->hostid); + } + + void handle_job(MonJobDoneMsg const* msg) { + job_done(msg->job_id); + } + + void handle_local_job(MonLocalJobBeginMsg const* msg) { + job_begin(msg->job_id, msg->hostid, msg->hostid); + } + + void handle_local_job(JobLocalDoneMsg const* msg) { + job_done(msg->job_id); + } + + void job_begin(uint32_t job_id, uint32_t source, uint32_t target) { + auto& job = active_jobs_[job_id]; + job.source = source; + job.target = target; + for (auto* observer : observers_) { + observer->added_job(this, job.source, job.target); + } + } + + void job_done(uint32_t job_id) { + auto it = active_jobs_.find(job_id); + if (it == active_jobs_.end()) { + assert(false); + return; + } + auto source = it->second.source; + auto target = it->second.target; + active_jobs_.erase(it); + for (auto* observer : observers_) { + observer->removed_job(this, source, target); + } + } + + void handle_stats(MonStatsMsg const* msg) { + std::cerr << msg->hostid << " " << msg->statmsg << "***" << std::endl; + + auto& machine = machines_[msg->hostid]; + auto const known = !machine.name.empty(); + if (update(msg->statmsg, &machine)) { + if (machine.name.empty()) { + machines_.erase(msg->hostid); + if (known) { + for (auto* observer : observers_) { + observer->removed_machine(this, msg->hostid); + } + } + } else { + if (known) { + for (auto* observer : observers_) { + observer->updated_machine(this, msg->hostid); + } + } else { + for (auto* observer : observers_) { + observer->added_machine(this, msg->hostid); + } + } + } + } else { + if (!known) { + machines_.erase(msg->hostid); + } + } + } + + bool update(std::string const& msg, Machine* machine) { + std::string name, ip; + unsigned max_jobs = 0; + + size_t last = 0; + while (true) { + auto pos = msg.find(':', last); + if (pos == std::string::npos) break; + auto end = msg.find('\n', pos + 1); + if (end == std::string::npos) end = msg.size(); + auto key = msg.substr(last, pos - last); + if (key == "Name") { + name = msg.substr(pos + 1, end - pos - 1); + auto dot = name.find('.'); + if (dot != std::string::npos) name = name.substr(0, dot); + } else if (key == "IP") { + ip = msg.substr(pos + 1, end - pos - 1); + } else if (key == "MaxJobs") { + errno = 0; + char* end_ptr; + auto tmp = strtoul(msg.c_str() + pos + 1, &end_ptr, 10); + if (errno == 0 && tmp > 0 && end_ptr == msg.c_str() + end) { + max_jobs = static_cast<unsigned>(tmp); + } + } + last = end + 1; + } + + if (name.empty()) name = ip; + + bool changed = false; + if (name != machine->name) { + machine->name = name; + changed = true; + } + if (max_jobs != machine->max_jobs) { + machine->max_jobs = max_jobs; + changed = true; + } + return changed; + } + + std::shared_ptr<Looper> looper_; + std::vector<Observer*> observers_; + std::unique_ptr<MsgChannel> channel_; + std::unique_ptr<DiscoverSched> discover_; + uint32_t discover_timer_; + bool connect_; + int discover_fd_; + + // Requested netname, hostname and port + std::string netname_; + std::string hostname_; + uint16_t port_; + + // Actually connected netname and hostname + std::string current_netname_; + std::string current_hostname_; + + std::unordered_map<uint32_t, Machine> machines_; + std::unordered_map<uint32_t, Job> active_jobs_; + std::unordered_map<uint32_t, uint32_t> pending_jobs_; +}; + +Monitor::Machine const MonitorImpl::EMPTY; + +} // namespace + +// static +Monitor* Monitor::create(std::shared_ptr<Looper> const& looper) { + return new MonitorImpl(looper); +} diff --git a/src/monitor.hh b/src/monitor.hh new file mode 100644 index 0000000..99e172b --- /dev/null +++ b/src/monitor.hh @@ -0,0 +1,62 @@ +#ifndef MONITOR_HH +#define MONITOR_HH + +#include <memory> +#include <string> + +class Looper; + +class Monitor { +public: + enum State { + SEARCHING, + CONNECTED, + }; + + struct Machine { + std::string name; + unsigned max_jobs; + + Machine() + : max_jobs(0) { + } + }; + + class Observer { + public: + virtual ~Observer() {} + virtual void state(Monitor* monitor, State state) = 0; + virtual void added_machine(Monitor* monitor, uint32_t id) = 0; + virtual void updated_machine(Monitor* monitor, uint32_t id) = 0; + virtual void removed_machine(Monitor* monitor, uint32_t id) = 0; + virtual void added_job(Monitor* monitor, uint32_t source_id, + uint32_t target_id) = 0; + virtual void removed_job(Monitor* monitor, uint32_t source_id, + uint32_t target_id) = 0; + + protected: + Observer() {} + }; + + virtual ~Monitor() {} + + static Monitor* create(std::shared_ptr<Looper> const& looper); + + virtual void connect(std::string const& netname = std::string(), + std::string const& scheduler = std::string(), + uint16_t port = 0) = 0; + virtual void disconnect() = 0; + virtual size_t machines() const = 0; + virtual uint32_t id(size_t index) const = 0; + virtual Machine machine_at(size_t index) const = 0; + virtual Machine machine(uint32_t id) const = 0; + + virtual void add_observer(Observer* observer) = 0; + +protected: + Monitor() {} + Monitor(Monitor const&) = delete; + Monitor& operator=(Monitor const&) = delete; +}; + +#endif // MONITOR_HH diff --git a/src/poll_looper.cc b/src/poll_looper.cc new file mode 100644 index 0000000..71788e0 --- /dev/null +++ b/src/poll_looper.cc @@ -0,0 +1,249 @@ +#include "common.hh" + +#include <poll.h> +#include <string.h> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "clock.hh" +#include "poll_looper.hh" + +namespace { + +void empty_callback(Looper*, int, uint8_t) { + assert(false); +} + +int const READ_POLL_MASK = POLLIN | POLLPRI; +int const WRITE_POLL_MASK = POLLOUT; + +class PollLooperImpl : public PollLooper { +public: + PollLooperImpl() + : next_id_(0), active_(0), exit_(false) { + } + + void add(int fd, uint8_t events, + std::function<void(Looper*, int, uint8_t)> const& callback) + override { + if (fd < 0) { + assert(false); + return; + } + auto it = fds_.find(fd); + if (it != fds_.end()) { + if (!removed_.count(it->second)) { + modify(fd, events); + return; + } + } + struct pollfd poll; + poll.fd = fd; + poll.events = 0; + if (events & EV_READ) { + poll.events |= READ_POLL_MASK; + } + if (events & EV_WRITE) { + poll.events |= WRITE_POLL_MASK; + } + if (free_.empty()) { + fds_[fd] = poll_.size(); + callback_.push_back(callback); + poll_.push_back(poll); + } else { + auto it = free_.begin(); + auto index = *it; + free_.erase(it); + fds_[fd] = index; + callback_[index] = callback; + poll_[index] = poll; + } + } + + void modify(int fd, uint8_t events) override { + auto it = fds_.find(fd); + if (it == fds_.end()) { + assert(false); + return; + } + auto& poll = poll_[it->second]; + poll.events = 0; + if (events & EV_READ) { + poll.events |= READ_POLL_MASK; + } + if (events & EV_WRITE) { + poll.events |= WRITE_POLL_MASK; + } + } + + void remove(int fd) override { + auto it = fds_.find(fd); + if (it == fds_.end()) { + assert(false); + return; + } + if (it->second < active_) { + removed_.insert(it->second); + } else { + free_.insert(it->second); + poll_[it->second].fd = -1; + poll_[it->second].events = 0; + callback_[it->second] = empty_callback; + fds_.erase(it); + } + } + + uint32_t schedule(double delay, + std::function<void(Looper*, uint32_t)> const& callback) + override { + assert(delay >= 0.0); + uint32_t id = next_id(); + auto target = clk::steady() + delay; + std::vector<Timer>::iterator insert = timer_.end(); + if (timer_.empty() || timer_.back().target >= target) { + auto it = timer_.begin(); + for (; it != timer_.end(); ++it) { + if (it->target >= target) { + insert = it; + break; + } + } + } + timer_.emplace(insert, id, target, callback); + return id; + } + + void cancel(uint32_t id) override { + auto active = active_ids_.find(id); + if (active == active_ids_.end()) { + assert(false); + return; + } + for (auto it = timer_.begin(); it != timer_.end(); ++it) { + if (it->id == id) { + active_ids_.erase(active); + timer_.erase(it); + break; + } + } + } + + void exit_when_empty() override { + exit_ = true; + } + + void run() override { + while (true) { + auto now = clk::steady(); + while (true) { + if (timer_.empty()) { + now = -1.0; + break; + } + if (now < timer_.front().target) { + now = timer_.front().target - clk::steady(); + if (now < 0.0) now = 0.0; + break; + } + auto id = timer_.front().id; + auto callback = timer_.front().callback; + timer_.erase(timer_.begin()); + active_ids_.erase(id); + callback(this, id); + } + active_ = poll_.size(); + while (active_ > 0 && poll_[active_ - 1].fd < 0) --active_; + if (exit_ && now < 0.0 && active_ == 0) return; + auto ret = poll(&poll_[0], active_, now < 0.0 ? -1 : now * 1000); + if (ret < 0) { + if (errno == EINTR) continue; + return; + } + for (size_t i = 0; ret && i < active_; ++i) { + if (poll_[i].revents) { + --ret; + if (removed_.count(i)) continue; + uint8_t events = 0; + if (poll_[i].revents & READ_POLL_MASK + && poll_[i].events & READ_POLL_MASK) { + events |= EV_READ; + } + if (poll_[i].revents & WRITE_POLL_MASK + && poll_[i].events & WRITE_POLL_MASK) { + events |= EV_WRITE; + } + if (poll_[i].revents & POLLHUP) { + if (poll_[i].events & READ_POLL_MASK) { + events |= EV_READ; + } else { + events |= EV_ERR; + } + } + if (poll_[i].revents & POLLERR) { + events |= EV_ERR; + } + if (poll_[i].revents & POLLNVAL) { + events |= EV_ERR; + removed_.insert(i); + } + if (events) { + callback_[i](this, poll_[i].fd, events); + } + } + } + + active_ = 0; + while (!removed_.empty()) { + auto index = *removed_.begin(); + removed_.erase(removed_.begin()); + auto it = fds_.find(poll_[index].fd); + if (it == fds_.end() || it->second != index) { + free_.insert(index); + poll_[index].fd = -1; + poll_[index].events = 0; + callback_[index] = empty_callback; + } else { + remove(it->first); + } + } + } + } + +private: + struct Timer { + uint32_t id; + double target; + std::function<void(Looper*, uint32_t)> callback; + + Timer(uint32_t id, double target, + std::function<void(Looper*, uint32_t)> const& callback) + : id(id), target(target), callback(callback) { + } + }; + + uint32_t next_id() { + uint32_t ret = ++next_id_; + if (ret == 0) return next_id(); + if (!active_ids_.emplace(ret).second) return next_id(); + return ret; + } + + std::vector<Timer> timer_; + uint32_t next_id_; + std::unordered_set<uint32_t> active_ids_; + std::vector<struct pollfd> poll_; + std::vector<std::function<void(Looper*, int, uint8_t)>> callback_; + std::unordered_set<size_t> free_; + std::unordered_map<int, size_t> fds_; + std::unordered_set<size_t> removed_; + size_t active_; + bool exit_; +}; + +} // namespace + +// static +PollLooper* PollLooper::create() { + return new PollLooperImpl(); +} diff --git a/src/poll_looper.hh b/src/poll_looper.hh new file mode 100644 index 0000000..16097fe --- /dev/null +++ b/src/poll_looper.hh @@ -0,0 +1,15 @@ +#ifndef POLL_LOOPER_HH +#define POLL_LOOPER_HH + +#include "looper.hh" + +class PollLooper : public Looper { +public: + static PollLooper* create(); + + virtual void run() = 0; + + virtual void exit_when_empty() = 0; +}; + +#endif // POLL_LOOPER_HH diff --git a/src/x.cc b/src/x.cc new file mode 100644 index 0000000..4af1230 --- /dev/null +++ b/src/x.cc @@ -0,0 +1,312 @@ +#include "common.hh" + +#include <mutex> +#include <unordered_map> + +#include "x.hh" + +namespace x { + +namespace { + +class AtomsImpl : public Atoms { +public: + explicit AtomsImpl(shared_connection const& conn) + : conn_(conn) { + } + + void preload(std::string const& name, bool create) override { + std::lock_guard<std::mutex> lock(mutex_); + auto& entry = data_[name]; + if (entry.state_ == UNINITIALIZED) { + entry.cookie_ = xcb_intern_atom( + conn_.get(), create ? 0 : 1, name.size(), name.c_str()); + entry.state_ = REQUESTED; + } + } + + xcb_atom_t get(std::string const& name, bool create) override { + std::lock_guard<std::mutex> lock(mutex_); + return get_locked(name, create); + } + +private: + enum State { + UNINITIALIZED = 0, + REQUESTED, + RESOLVED, + }; + + struct Entry { + State state_; + xcb_atom_t atom_; + xcb_intern_atom_cookie_t cookie_; + + Entry() + : state_(UNINITIALIZED) { + } + }; + + xcb_atom_t get_locked(std::string const& name, bool create) { + auto& entry = data_[name]; + switch (entry.state_) { + case UNINITIALIZED: + entry.cookie_ = xcb_intern_atom( + conn_.get(), create ? 0 : 1, name.size(), name.c_str()); + entry.state_ = REQUESTED; + // Fallthrough + case REQUESTED: { + auto reply = xcb_intern_atom_reply(conn_.get(), entry.cookie_, nullptr); + if (reply) { + entry.atom_ = reply->atom; + free(reply); + } + entry.state_ = RESOLVED; + // Fallthrough + } + case RESOLVED: + if (create && entry.atom_ == XCB_ATOM_NONE) { + entry.state_ = UNINITIALIZED; + return get_locked(name, create); + } + break; + } + return entry.atom_; + } + + shared_connection conn_; + std::mutex mutex_; + std::unordered_map<std::string, Entry> data_; +}; + +class EwmhImpl : public Ewmh { +public: + EwmhImpl(shared_connection const& conn, int screen) + : conn_(conn), screen_(screen) { + if (conn_) { + cookies_ = xcb_ewmh_init_atoms(conn_.get(), &ewmh_); + } else { + cookies_ = nullptr; + } + } + + ~EwmhImpl() override { + ensure_cookies(); + if (supported_) xcb_ewmh_get_atoms_reply_wipe(supported_.get()); + if (conn_) xcb_ewmh_connection_wipe(&ewmh_); + } + + xcb_ewmh_connection_t* conn() override { + ensure_supported(); + return conn_ ? &ewmh_ : nullptr; + } + + bool supported(xcb_atom_t atom) override { + ensure_supported(); + if (!conn_) return false; + for (uint32_t i = 0; i < supported_->atoms_len; i++) { + if (supported_->atoms[i] == atom) return true; + } + return false; + } + +private: + void ensure_cookies() { + if (!cookies_) return; + if (!xcb_ewmh_init_atoms_replies(&ewmh_, cookies_, nullptr)) { + conn_.reset(); + } + cookies_ = nullptr; + } + + void ensure_supported() { + if (supported_ || !conn_) return; + ensure_cookies(); + auto cookie = xcb_ewmh_get_supported(&ewmh_, screen_); + supported_.reset(new xcb_ewmh_get_atoms_reply_t()); + if (!xcb_ewmh_get_supported_reply(&ewmh_, cookie, supported_.get(), + nullptr)) { + conn_.reset(); + } + } + + shared_connection conn_; + int const screen_; + xcb_ewmh_connection_t ewmh_; + xcb_intern_atom_cookie_t* cookies_; + std::unique_ptr<xcb_ewmh_get_atoms_reply_t> supported_; +}; + +} // namespace + +// static +Atoms* Atoms::create(shared_connection const& conn) { + return new AtomsImpl(conn); +} + +// static +Ewmh* Ewmh::create(shared_connection const& conn, int screen) { + return new EwmhImpl(conn, screen); +} + +xcb_screen_t const* get_screen(xcb_connection_t* conn, int screen) { + auto setup = xcb_get_setup(conn); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + while (screen--) { + xcb_screen_next(&iter); + } + return iter.data; +} + +void prefetch_extensions(xcb_connection_t* conn) { + xcb_prefetch_extension_data(conn, &xcb_render_id); +} + +enum PixelFormat { + ABGR_8888, + ARGB_8888, + BGRA_8888, + RGBA_8888, +}; + +Format get_best_format(xcb_connection_t* conn, xcb_screen_t const* screen, + bool allow_render) { + assert(conn); + uint8_t depth = 0; + xcb_visualtype_t* visual = nullptr; + xcb_render_pictforminfo_t* render = nullptr; + xcb_render_pictforminfo_t* render_alpha = nullptr; + xcb_render_query_pict_formats_reply_t* render_formats = nullptr; + PixelFormat format; + + auto render_reply = xcb_get_extension_data(conn, &xcb_render_id); + if (render_reply && allow_render && render_reply->present) { + auto version = xcb_render_query_version_unchecked( + conn, XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION); + auto formats = xcb_render_query_pict_formats_unchecked(conn); + auto version_reply = xcb_render_query_version_reply(conn, version, nullptr); + if (!version_reply || (version_reply->major_version == 0 + && version_reply->minor_version < 6)) { + xcb_discard_reply(conn, formats.sequence); + } else { + render_formats = xcb_render_query_pict_formats_reply( + conn, formats, nullptr); + } + free(version_reply); + } + auto count = xcb_screen_allowed_depths_length(screen); + if (count == 0) { + free(render_formats); + return Format(); + } + auto iter = xcb_screen_allowed_depths_iterator(screen); + while (count--) { + auto d = iter.data; + if (d->depth <= depth) continue; + auto visuals_count = xcb_depth_visuals_length(d); + if (visuals_count == 0) continue; + auto v = xcb_depth_visuals(d); + for (; visuals_count--; ++v) { + switch (v->_class) { + case XCB_VISUAL_CLASS_TRUE_COLOR: + case XCB_VISUAL_CLASS_DIRECT_COLOR: + break; + default: + continue; + } + PixelFormat f; + if (v->bits_per_rgb_value != 8) continue; + if (v->red_mask == 0xff0000 && v->green_mask == 0xff00 + && v->blue_mask == 0xff) { + f = ARGB_8888; + } else if (v->red_mask == 0xff && v->green_mask == 0xff00 + && v->blue_mask == 0xff0000) { + f = ABGR_8888; + } else if (v->red_mask == 0xff000000 && v->green_mask == 0xff0000 + && v->blue_mask == 0xff00) { + f = RGBA_8888; + } else if (v->red_mask == 0xff00 && v->green_mask == 0xff0000 + && v->blue_mask == 0xff000000) { + f = BGRA_8888; + } else { + continue; + } + visual = v; + format = f; + depth = d->depth; + if (!render_formats) break; + auto info = xcb_render_query_pict_formats_formats(render_formats); + auto count = xcb_render_query_pict_formats_formats_length(render_formats); + for (; count--; ++info) { + if (info->type != XCB_RENDER_PICT_TYPE_DIRECT) continue; + if (info->depth != d->depth) continue; + if (info->direct.alpha_mask != 0 + || (static_cast<uint32_t>(info->direct.red_mask) + << info->direct.red_shift) != visual->red_mask + || (static_cast<uint32_t>(info->direct.green_mask) + << info->direct.green_shift) != visual->green_mask + || (static_cast<uint32_t>(info->direct.blue_mask) + << info->direct.blue_shift) != visual->blue_mask) continue; + render = info; + break; + } + if (render) break; + } + xcb_depth_next(&iter); + } + if (!depth) { + free(render_formats); + return Format(); + } + if (render && render_formats) { + auto info = xcb_render_query_pict_formats_formats(render_formats); + auto count = xcb_render_query_pict_formats_formats_length(render_formats); + for (; count--; ++info) { + if (info->type != XCB_RENDER_PICT_TYPE_DIRECT) continue; + if (info->depth != 32) continue; + if (info->direct.alpha_mask != 0xff + || info->direct.red_mask != 0xff + || info->direct.green_mask != 0xff + || info->direct.blue_mask != 0xff) continue; + + PixelFormat f; + if (info->direct.red_shift == 0 + && info->direct.green_shift == 8 + && info->direct.blue_shift == 16 + && info->direct.alpha_shift == 24) { + f = ABGR_8888; + } else if (info->direct.red_shift == 16 + && info->direct.green_shift == 8 + && info->direct.blue_shift == 0 + && info->direct.alpha_shift == 24) { + f = ARGB_8888; + } else if (info->direct.red_shift == 8 + && info->direct.green_shift == 16 + && info->direct.blue_shift == 24 + && info->direct.alpha_shift == 0) { + f = BGRA_8888; + } else if (info->direct.red_shift == 24 + && info->direct.green_shift == 16 + && info->direct.blue_shift == 8 + && info->direct.alpha_shift == 0) { + f = RGBA_8888; + } else { + continue; + } + + if (!render_alpha || f == format) { + render = info; + } + } + if (!render) { + render = nullptr; + } + } + // Leak render_formats here to be able to return render and render_alpha + // as xcb_render_pictforminfo_t pointers + // free(render_formats); + + return Format(depth, visual, render, render_alpha); +} + +} // namespace x diff --git a/src/x.hh b/src/x.hh new file mode 100644 index 0000000..ce4f812 --- /dev/null +++ b/src/x.hh @@ -0,0 +1,255 @@ +#ifndef X_HH +#define X_HH + +#include <memory> +#include <xcb/render.h> +#include <xcb/xcb.h> +#include <xcb/xcb_ewmh.h> +#include <xcb/xproto.h> +#include <xcb/render.h> + +namespace x { + +namespace priv { +struct XcbConnectionDelete { + void operator()(xcb_connection_t* ptr) const { + xcb_disconnect(ptr); + } +}; + +struct XcbWindowDelete { + void operator()(xcb_connection_t* conn, xcb_window_t id) const { + xcb_destroy_window(conn, id); + } +}; + +struct XcbPixmapDelete { + void operator()(xcb_connection_t* conn, xcb_window_t id) const { + xcb_free_pixmap(conn, id); + } +}; + +struct XcbGContextDelete { + void operator()(xcb_connection_t* conn, xcb_gcontext_t id) const { + xcb_free_gc(conn, id); + } +}; + +struct XcbRenderPictureDelete { + void operator()(xcb_connection_t* conn, xcb_render_picture_t pic) const { + xcb_render_free_picture(conn, pic); + } +}; + +} // priv + +class shared_connection { +public: + shared_connection() { + } + shared_connection(std::nullptr_t) { + } + explicit shared_connection(xcb_connection_t* conn) + : shared_(conn, priv::XcbConnectionDelete()) { + } + shared_connection(shared_connection const& shared) + : shared_(shared.shared_) { + } + shared_connection(shared_connection&& shared) + : shared_(std::move(shared.shared_)) { + } + + shared_connection& operator=(shared_connection const& shared) { + shared_ = shared.shared_; + return *this; + } + + shared_connection& operator=(shared_connection&& shared) { + shared_ = std::move(shared.shared_); + return *this; + } + + void swap(shared_connection& shared) { + shared_.swap(shared.shared_); + } + + void reset() { + shared_.reset(); + } + + void reset(xcb_connection_t* ptr) { + shared_.reset(ptr, priv::XcbConnectionDelete()); + } + + xcb_connection_t* get() const { + return shared_.get(); + } + + explicit operator bool() const { + return shared_.get() != nullptr; + } + +private: + std::shared_ptr<xcb_connection_t> shared_; +}; + +template<typename T, typename D> +class unique_resource { +public: + unique_resource() + : id_(XCB_NONE) { + } + explicit unique_resource(shared_connection const& conn) + : conn_(conn), id_(conn ? xcb_generate_id(conn.get()) : XCB_NONE) { + } + unique_resource(shared_connection const& conn, T id) + : conn_(conn), id_(id) { + if (id_ == XCB_NONE) conn_.reset(); + } + unique_resource(unique_resource&& res) + : conn_(res.conn_), id_(res.release()) { + } + + ~unique_resource() { + destroy(); + } + + unique_resource& operator=(shared_connection const& conn) { + reset(conn); + return *this; + } + + unique_resource& operator=(unique_resource&& res) { + swap(res); + return *this; + } + + xcb_connection_t* conn() const { + return conn_.get(); + } + + shared_connection const& shared_conn() const { + return conn_; + } + + T get() const { + return id_; + } + + explicit operator bool() const { + return id_ != XCB_NONE; + } + + void reset() { + destroy(); + conn_.reset(); + id_ = XCB_NONE; + } + + void reset(shared_connection const& conn) { + if (conn) { + reset(conn, xcb_generate_id(conn.get())); + } else { + reset(); + } + } + + void reset(shared_connection const& conn, T id) { + destroy(); + conn_ = conn; + id_ = id; + if (id_ == XCB_NONE) conn_.reset(); + } + + void swap(unique_resource& res) { + conn_.swap(res.conn_); + auto tmp = id_; + id_ = res.id_; + res.id_ = tmp; + } + +private: + unique_resource(unique_resource const&) = delete; + unique_resource& operator=(unique_resource const&) = delete; + + void destroy() { + if (id_ == XCB_NONE) return; + D()(conn_.get(), id_); + } + + T release() { + T ret = id_; + id_ = XCB_NONE; + conn_.reset(); + return ret; + } + + shared_connection conn_; + T id_; +}; + +typedef unique_resource<xcb_window_t, priv::XcbWindowDelete> unique_window; +typedef unique_resource<xcb_pixmap_t, priv::XcbPixmapDelete> unique_pixmap; +typedef unique_resource<xcb_gcontext_t, + priv::XcbGContextDelete> unique_gcontext; +typedef unique_resource<xcb_render_picture_t, + priv::XcbRenderPictureDelete> unique_picture; + +struct Format { + uint8_t depth; + xcb_visualtype_t* visual; + xcb_render_pictforminfo_t* render; + xcb_render_pictforminfo_t* render_alpha; + + bool good() const { + return depth > 0 && visual; + } + + Format(uint8_t depth, xcb_visualtype_t* visual, + xcb_render_pictforminfo_t* render, + xcb_render_pictforminfo_t* render_alpha) + : depth(depth), visual(visual), render(render), render_alpha(render_alpha) { + } + Format() + : depth(0), visual(nullptr), render(nullptr), render_alpha(nullptr) { + } +}; + +xcb_screen_t const* get_screen(xcb_connection_t* conn, int screen); +void prefetch_extensions(xcb_connection_t* conn); +Format get_best_format(xcb_connection_t* conn, xcb_screen_t const* screen, + bool allow_render = true); + +class Atoms { +public: + virtual ~Atoms() {} + + static Atoms* create(shared_connection const& conn); + + virtual void preload(std::string const& name, bool create = true) = 0; + virtual xcb_atom_t get(std::string const& name, bool create = true) = 0; + +protected: + Atoms() {} + Atoms(Atoms const&) = delete; + Atoms& operator=(Atoms const&) = delete; +}; + +class Ewmh { +public: + virtual ~Ewmh() {} + + static Ewmh* create(shared_connection const& conn, int screen); + + virtual xcb_ewmh_connection_t* conn() = 0; + virtual bool supported(xcb_atom_t atom) = 0; + +protected: + Ewmh() {} + Ewmh(Ewmh const&) = delete; + Ewmh& operator=(Ewmh const&) = delete; +}; + +} // namespace x + +#endif // X_HH |
