#include "common.hh" #include #include #include "animation.hh" #include "args.hh" #include "blissful_monitor.hh" #include "cairo.hh" #include "fake_monitor.hh" #include "icecc.hh" #include "monitor.hh" #include "pango.hh" namespace { class IceccMonMonImpl : public IceccMonMon, virtual Monitor::Observer { public: IceccMonMonImpl(std::shared_ptr const& looper, unsigned columns) : IceccMonMon(looper), connected_(false), max_jobs_(0), jobs_(0), requests_(0), force_columns_(columns) { } void connect(Args const* args) override { std::unique_ptr monitor; #if FAKE_MONITOR monitor = FakeMonitor::create(looper_); #else monitor = Monitor::create(looper_); #endif monitor_ = BlissfulMonitor::create(looper_, std::move(monitor)); monitor_->add_observer(this); monitor_->connect(args->arg("network", ""), args->arg("scheduler", "")); } protected: void width_changed() override { job_pattern_.reset(); } #if FAKE_MONITOR void do_toggle_fakes() override { monitor_->toggle_fakes(); } #endif 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(Machine const&) = delete; Machine& operator=(Machine const&) = delete; ~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 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() override { if (!animator_) return; for (auto& machine : machines_) { if (machine->animation) animator_->stop(machine->animation.get()); } } void internal_quit() override { monitor_->disconnect(); } 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_content(cairo_t* cairo, PangoLayout* layout, uint16_t w, uint16_t h) override { auto const pad_x = std::min(9.0, w / 20.0), pad_y = pad_x; auto const margin_x = std::min(7.5, w / 20.0), margin_y = margin_x; auto y = pad_y; auto const columns = force_columns_ ? force_columns_ : std::max(1, w / h); auto const box_width = (w - pad_x) / columns - pad_x; auto const 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, (box_width - margin_x * 2) * PANGO_SCALE); unsigned col = 0; for (auto const& machine : machines_) { pango_layout_set_text(layout, machine->data.name.data(), machine->data.name.size()); auto x = pad_x + col * (box_width + pad_x); rounded_path(cairo, x, y, box_width, box_height); cairo_set_source_rgba(cairo, 0.1, 0.1, 0.1, 0.7); if (machine->x > 0.0) { cairo_fill_preserve(cairo); auto old = cairo::unique_path(cairo_copy_path(cairo)); cairo_new_path(cairo); cairo_rectangle(cairo, x, y, machine->x * box_width, box_height); cairo_clip(cairo); cairo_append_path(cairo, old.get()); cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, -x, 0); cairo_pattern_set_matrix(job_pattern_.get(), &matrix); cairo_set_source(cairo, job_pattern_.get()); cairo_fill(cairo); cairo_reset_clip(cairo); } else { cairo_fill(cairo); } if (machine->requests > 0) { auto radius = box_height / 10.0; cairo_set_line_width(cairo, 1.5); cairo_new_path(cairo); cairo_move_to(cairo, x + box_width - radius, y); cairo_rel_line_to(cairo, -(machine->requests * (box_width - radius * 2)) / requests_, 0); cairo_set_source_rgb(cairo, 0, 0.6, 0); cairo_stroke(cairo); } cairo_move_to(cairo, x + margin_x, y + margin_y); pango_cairo_layout_path(cairo, layout); cairo_set_source_rgb(cairo, 0.0, 0.0, 0.0); cairo_set_line_cap(cairo, CAIRO_LINE_CAP_ROUND); cairo_set_line_width(cairo, 2.0); cairo_stroke(cairo); cairo_set_source_rgb(cairo, 1.0, 1.0, 1.0); cairo_move_to(cairo, x + margin_x, y + margin_y); pango_cairo_show_layout(cairo, layout); if (++col == columns) { col = 0; y += box_height + pad_y * 2; if (y >= h) break; } } } void added_machine(Monitor*, uint32_t id) override { auto machine = std::make_unique(id); update(machine.get()); machines_.emplace( std::lower_bound(machines_.begin(), machines_.end(), machine, compare_machine), std::move(machine)); draw(); } static bool compare_machine(std::unique_ptr const& m1, std::unique_ptr const& m2) { return *m1 < *m2; } void updated_machine(Monitor*, uint32_t id) override { for (auto& machine : machines_) { if (machine->id == id) { auto old = machine->data.name; update(machine.get()); if (machine->data.name != old) { // TODO: Perhaps remove and insert instead? std::sort(machines_.begin(), machines_.end(), compare_machine); } 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.get()); } } } else { for (auto& machine : machines_) { if (machine->id == source) { ++machine->requests; } if (machine->id == target) { ++machine->jobs; animate(machine.get()); } } ++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.get()); } } } else { for (auto& machine : machines_) { if (machine->id == source) { --machine->requests; } if (machine->id == target) { --machine->jobs; animate(machine.get()); } } --requests_; } --jobs_; draw(); } void animate(Machine* machine) { if (!animator_) return; if (machine->animation) return; machine->animation = std::make_unique(machine); animator_->start(machine->animation, machine); } std::unique_ptr monitor_; bool connected_; std::vector> machines_; unsigned max_jobs_; unsigned jobs_; unsigned requests_; cairo::unique_pattern job_pattern_; unsigned force_columns_; }; } // namespace IceccMonMon::IceccMonMon(std::shared_ptr const& looper) : MonMon(looper) {} std::unique_ptr create_icecc_monmon( std::shared_ptr const& looper, unsigned columns) { return std::make_unique(looper, columns); }