summaryrefslogtreecommitdiff
path: root/src/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cc')
-rw-r--r--src/main.cc872
1 files changed, 872 insertions, 0 deletions
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;
+}