summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-09-26 20:09:31 +0200
committerJoel Klinghed <the_jk@yahoo.com>2017-09-26 20:09:31 +0200
commitc85b624d28564a6f785b25000e2b7825592a919d (patch)
tree647b756c824b470b35f1371eb869e9534ed6c1bb
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--meson.build48
-rw-r--r--meson_options.txt1
-rw-r--r--src/args.cc323
-rw-r--r--src/args.hh64
-rw-r--r--src/clock.cc24
-rw-r--r--src/clock.hh13
-rw-r--r--src/common.hh10
-rw-r--r--src/fake_monitor.cc191
-rw-r--r--src/fake_monitor.hh11
-rw-r--r--src/io.cc167
-rw-r--r--src/io.hh87
-rw-r--r--src/looper.cc10
-rw-r--r--src/looper.hh31
-rw-r--r--src/main.cc872
-rw-r--r--src/monitor.cc410
-rw-r--r--src/monitor.hh62
-rw-r--r--src/poll_looper.cc249
-rw-r--r--src/poll_looper.hh15
-rw-r--r--src/x.cc312
-rw-r--r--src/x.hh255
21 files changed, 3156 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/build \ No newline at end of file
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..c61b6f7
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,48 @@
+project('monmon', 'cpp',
+ version: '0.1',
+ default_options: ['cpp_std=c++11'])
+
+cpp_optional_flags = ['-fno-rtti', '-fno-exceptions',
+ '-fvisibility=hidden',
+ '-Wall', '-Wextra',
+ '-Wno-missing-field-initializers',
+ '-Wno-maybe-uninitialized']
+cpp_flags = ['-DHAVE_CONFIG_H']
+if get_option('buildtype') == 'release'
+ # If asserts are disabled parameters and variables used for only that
+ # end up causing warnings
+ cpp_optional_flags += ['-Wno-unused-parameter', '-Wno-unused-variable']
+ cpp_flags += '-DNDEBUG'
+endif
+cpp = meson.get_compiler('cpp')
+foreach flag : cpp_optional_flags
+ if cpp.has_argument(flag)
+ cpp_flags += flag
+ endif
+endforeach
+add_global_arguments(cpp_flags, language: 'cpp')
+
+conf = configuration_data()
+conf.set('VERSION', '"' + meson.project_version() + '"')
+conf.set10('FAKE_MONITOR', get_option('fake_monitor'))
+
+xcb_deps = [dependency('xcb', version : '>= 1.10'),
+ dependency('xcb-image', version : '>= 0.3.8'),
+ dependency('xcb-event', version : '>= 0.3.8'),
+ dependency('xcb-ewmh', version : '>= 0.3.8'),
+ dependency('xcb-keysyms', version : '>= 0.3.8'),
+ dependency('xcb-icccm', version : '>= 0.3.8')]
+dep_cairo = dependency('cairo-xcb', version : '>= 1.12.0')
+dep_pango = dependency('pangocairo', version : '>= 1.40.0')
+dep_icecc = dependency('icecc', version : '>= 1.1')
+dep_thread = dependency('threads')
+
+configure_file(output: 'config.h',
+ configuration: conf)
+
+executable('monmon',
+ sources: ['src/main.cc', 'src/x.cc', 'src/args.cc',
+ 'src/poll_looper.cc', 'src/looper.cc', 'src/clock.cc',
+ 'src/io.cc', 'src/monitor.cc', 'src/fake_monitor.cc'],
+ dependencies: [dep_thread, dep_pango, dep_cairo, dep_icecc,
+ xcb_deps])
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..2331695
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1 @@
+option('fake_monitor', type: 'boolean', value: false, description: 'Connect to a fake monitor to keeps generating job events')
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