diff options
| author | Joel Klinghed <the_jk@yahoo.com> | 2017-09-26 20:09:31 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@yahoo.com> | 2017-09-26 20:09:31 +0200 |
| commit | c85b624d28564a6f785b25000e2b7825592a919d (patch) | |
| tree | 647b756c824b470b35f1371eb869e9534ed6c1bb /src/main.cc | |
Initial commit
Diffstat (limited to 'src/main.cc')
| -rw-r--r-- | src/main.cc | 872 |
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; +} |
