summaryrefslogtreecommitdiff
path: root/src/monmon.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@opera.com>2020-07-17 11:45:25 +0200
committerJoel Klinghed <the_jk@opera.com>2020-07-17 11:45:25 +0200
commiteb5fd01c5aa7759bc626b9604bc786ea6c492a35 (patch)
tree7a4309c02c7f25d86715b19d4153da5eb15aa3b8 /src/monmon.cc
parent97d7b6692202c755c8f47f3ae68f2675506c1c25 (diff)
Break out MonMon parts that are unrelated to Icecc to a separate class
Diffstat (limited to 'src/monmon.cc')
-rw-r--r--src/monmon.cc455
1 files changed, 455 insertions, 0 deletions
diff --git a/src/monmon.cc b/src/monmon.cc
new file mode 100644
index 0000000..4335acb
--- /dev/null
+++ b/src/monmon.cc
@@ -0,0 +1,455 @@
+#include "common.hh"
+
+#include <iostream>
+#include <math.h>
+#include <xcb/xcb_icccm.h>
+
+#include "monmon.hh"
+
+namespace {
+
+struct MwmHints {
+ uint32_t flags;
+ uint32_t functions;
+ uint32_t decorations;
+ int32_t input_mode;
+ uint32_t status;
+};
+
+} // namespace
+
+MonMon::MonMon(std::shared_ptr<PollLooper> const& looper)
+ : looper_(looper), animator_(Animator::create(looper)),
+ depth_(0), black_pixel_(0), screen_(nullptr), 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::~MonMon() {
+ stop_all_animations();
+ unset_desktop_window();
+ close_pipe();
+ wnd_.reset();
+}
+
+void MonMon::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);
+ cmap_.reset();
+
+ uint32_t values[4];
+ size_t i = 0;
+ uint32_t mask = XCB_CW_BACK_PIXEL;
+ values[i++] = screen->black_pixel;
+ if (format.need_border()) {
+ mask |= XCB_CW_BORDER_PIXEL;
+ values[i++] = screen->black_pixel;
+ }
+ mask |= XCB_CW_EVENT_MASK;
+ values[i++] = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_EXPOSURE
+ | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ if (format.need_colormap()) {
+ mask |= XCB_CW_COLORMAP;
+ cmap_.reset(conn);
+ xcb_create_colormap(cmap_.conn(), XCB_COLORMAP_ALLOC_NONE, cmap_.get(),
+ screen->root, format.visual->visual_id);
+ values[i++] = cmap_.get();
+ }
+ 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,
+ 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;
+ }
+ }
+ auto wm = ewmh->conn();
+ if (wm) {
+ xcb_ewmh_set_wm_name(wm, wnd_.get(), 6, "MonMon");
+ } else {
+ xcb_icccm_set_wm_name(wnd_.conn(), wnd_.get(), XCB_ATOM_STRING, 8,
+ 6, "MonMon");
+ }
+ 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 font = pango::unique_font_description(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 = pango::unique_font_metrics(
+ 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 MonMon::quit_from_xcb() {
+ io::write(pipe_, "q", 1);
+}
+
+#if FAKE_MONITOR
+void MonMon::toggle_fakes() {
+ io::write(pipe_, " ", 1);
+}
+#endif
+
+void MonMon::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);
+}
+
+void MonMon::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) {
+ width_changed();
+ 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 MonMon::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 MonMon::update_desktop_window(xcb_window_t window, xcb_atom_t property) {
+ if (window != desktop_window_) return;
+ if (property != rootpmap_) return;
+
+ update_desktop_pixmap();
+}
+
+void MonMon::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 MonMon::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;
+ 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 MonMon::close_pipe() {
+ if (!pipe_) return;
+ looper_->remove(pipe_.read());
+ pipe_.reset();
+}
+
+void MonMon::pipe(Looper*, int, uint8_t event) {
+ if (event & Looper::EV_READ) {
+ char tmp;
+ if (io::read(pipe_, &tmp, 1)) {
+ if (tmp == 'd') {
+ force_draw();
+ return;
+ }
+#if FAKE_MONITOR
+ if (tmp == ' ') {
+ do_toggle_fakes();
+ return;
+ }
+#endif
+ }
+ }
+
+ stop_all_animations();
+ animator_.reset();
+ internal_quit();
+ looper_->exit_when_empty();
+ close_pipe();
+}
+
+void MonMon::draw() {
+ // Animator will draw soon anyway, so let it
+ if (animator_ && animator_->active()) return;
+ force_draw();
+}
+
+// static
+void MonMon::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 MonMon::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());
+
+ draw_content(cairo_.get(), layout_.get(), w_, h_);
+
+ cairo_surface_flush(surface_.get());
+ xcb_copy_area(wnd_.conn(), pixmap_.get(), wnd_.get(), gcontext_.get(),
+ 0, 0, 0, 0, w_, h_);
+}
+
+void MonMon::force_draw() {
+ internal_draw();
+ xcb_flush(wnd_.conn());
+}
+
+void MonMon::tick(Animator*) {
+ force_draw();
+}
+
+// static
+void MonMon::preload(x::Atoms* atoms) {
+ atoms->preload("_MOTIF_WM_INFO", false);
+ atoms->preload("_MOTIF_WM_HINTS");
+ atoms->preload("_XROOTPMAP_ID", false);
+}