#include "common.hh" #include #include #include #include #include #include #include #include #include #include #include #include "animation.hh" #include "animator.hh" #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 PangoFontDescriptionDelete { void operator()(PangoFontDescription* ptr) const { pango_font_description_free(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, virtual Animator::Observer { public: explicit MonMon(std::shared_ptr const& looper) : looper_(looper), animator_(Animator::create(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)); animator_->add_observer(this); } ~MonMon() { stop_all_animations(); 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 const& atoms, std::shared_ptr 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(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 font = std::unique_ptr( pango_font_description_new()); pango_font_description_set_size(font.get(), 10 * PANGO_SCALE); pango_layout_set_font_description(layout_.get(), font.get()); auto context = pango_layout_get_context(layout_.get()); auto metrics = std::unique_ptr( pango_context_get_metrics(context, nullptr, nullptr)); box_height_ = static_cast( 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 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(&event)); xcb_flush(wnd_.conn()); } */ void expose(int16_t x, int16_t y, uint16_t w, uint16_t h) { std::lock_guard 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 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: class MachineAnimation; struct Machine : virtual Animator::AnimationObserver { uint32_t id; Monitor::Machine data; unsigned jobs; unsigned requests; double x; std::shared_ptr animation; explicit Machine(uint32_t id) : id(id), jobs(0), requests(0), x(0.0) { } ~Machine() override { assert(!animation); } 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 ticked(Animator*, Animation*, double value) override { x = value; } void stopped(Animator*, Animation*) override { x = static_cast(jobs) / data.max_jobs; animation.reset(); } }; class MachineAnimation : public Animation { public: explicit MachineAnimation(Machine* machine) : machine_(machine), last_(0.0) { } bool tick(double duration, double* value) override { auto target = static_cast(machine_->jobs) / machine_->data.max_jobs; if (duration == 0.0) { last_ = duration; *value = machine_->x; return machine_->x != target; } else { double diff = target - machine_->x; double max = (duration - last_) * .5; last_ = duration; if (fabs(diff) > max) { diff = diff < 0.0 ? -max : max; *value = machine_->x + diff; return true; } else { *value = target; return false; } } } private: Machine* machine_; double last_; }; 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_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; depth = 0; } 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; } } } stop_all_animations(); animator_.reset(); monitor_->disconnect(); looper_->exit_when_empty(); close_pipe(); } void state(Monitor*, Monitor::State state) override { switch (state) { case Monitor::SEARCHING: connected_ = false; stop_all_animations(); machines_.clear(); max_jobs_ = 0; jobs_ = 0; requests_ = 0; if (!wnd_) return; draw(); break; case Monitor::CONNECTED: connected_ = true; draw(); break; } } void stop_all_animations() { if (!animator_) return; for (auto& machine : machines_) { if (machine.animation) animator_->stop(machine.animation.get()); } } 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; if (machine.jobs) animate(machine); } else if (old > machine.data.max_jobs) { max_jobs_ -= old - machine.data.max_jobs; if (machine.jobs) animate(machine); } } void draw() { // Animator will draw soon anyway, so let it if (animator_ && animator_->active()) return; tick(nullptr); } 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 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.x > 0.0) { cairo_fill_preserve(cairo_.get()); auto old = std::unique_ptr( cairo_copy_path(cairo_.get())); cairo_new_path(cairo_.get()); cairo_rectangle(cairo_.get(), pad_x, y, machine.x * box_width, 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) { auto radius = box_height / 10.0; cairo_set_line_width(cairo_.get(), 1.5); cairo_new_path(cairo_.get()); cairo_move_to(cairo_.get(), pad_x + box_width - radius, y); cairo_rel_line_to(cairo_.get(), -(machine.requests * (box_width - radius * 2)) / 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; if (animator_) animator_->stop(it->animation.get()); 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; animate(machine); } } } else { for (auto& machine : machines_) { if (machine.id == source) { ++machine.requests; } if (machine.id == target) { ++machine.jobs; animate(machine); } } ++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; animate(machine); } } } else { for (auto& machine : machines_) { if (machine.id == source) { --machine.requests; } if (machine.id == target) { --machine.jobs; animate(machine); } } --requests_; } --jobs_; draw(); } void tick(Animator*) override { internal_draw(); xcb_flush(wnd_.conn()); } void animate(Machine& machine) { if (!animator_) return; if (machine.animation) return; machine.animation.reset(new MachineAnimation(&machine)); animator_->start(machine.animation, &machine); } std::shared_ptr looper_; std::unique_ptr monitor_; std::unique_ptr animator_; bool connected_; io::pipe pipe_; std::shared_ptr 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 machines_; unsigned max_jobs_; unsigned jobs_; unsigned requests_; std::mutex mutex_; std::unique_ptr surface_; std::unique_ptr cairo_; std::unique_ptr job_pattern_; std::unique_ptr 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 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 const& atoms, std::shared_ptr const& monmon) { std::unique_ptr syms; syms.reset(xcb_key_symbols_alloc(conn.get())); std::unique_ptr 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(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(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(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(event.get()); if (monmon->match(e->window)) { monmon->quit_from_xcb(); return; } break; } case XCB_CONFIGURE_NOTIFY: { auto e = reinterpret_cast(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(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(event.get()); xcb_refresh_keyboard_mapping(syms.get(), e); break; } case XCB_REPARENT_NOTIFY: { auto e = reinterpret_cast(event.get()); if (monmon->match(e->window)) { monmon->update_desktop_window(); } break; } case XCB_PROPERTY_NOTIFY: { auto e = reinterpret_cast(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::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 looper(PollLooper::create()); std::shared_ptr 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 ewmh(x::Ewmh::create(conn, screen_index)); auto monmon = std::make_shared(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; }