diff options
| author | Joel Klinghed <the_jk@opera.com> | 2020-07-17 11:45:25 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@opera.com> | 2020-07-17 11:45:25 +0200 |
| commit | eb5fd01c5aa7759bc626b9604bc786ea6c492a35 (patch) | |
| tree | 7a4309c02c7f25d86715b19d4153da5eb15aa3b8 /src/monmon.cc | |
| parent | 97d7b6692202c755c8f47f3ae68f2675506c1c25 (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.cc | 455 |
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); +} |
