#include "common.hh" #include #include #include #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 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), draw_requested_(false) { 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 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); 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(); auto title_str = title(); if (wm) { xcb_ewmh_set_wm_name(wm, wnd_.get(), title_str.size(), title_str.data()); } else { xcb_icccm_set_wm_name(wnd_.conn(), wnd_.get(), XCB_ATOM_STRING, 8, title_str.size(), title_str.data()); } 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 = 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( 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 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; mark_all_dirty(); 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) { 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_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(); mark_all_dirty(); io::write(pipe_, "d", 1); } void MonMon::close_pipe() { if (!pipe_) return; looper_->remove(pipe_.read()); pipe_.reset(); } pango::unique_layout MonMon::make_layout() const { return pango::unique_layout(pango_layout_copy(layout_.get())); } void MonMon::fill_background(cairo_t* cairo, xcb_rectangle_t const& r) { cairo_save(cairo); cairo_rectangle(cairo, r.x, r.y, r.width, r.height); cairo_clip(cairo); if (!desktop_surface_) { cairo_set_source_rgb(cairo, 0, 0, 0); } else { cairo_set_source_surface(cairo, desktop_surface_.get(), -x_, -y_); } cairo_paint(cairo); cairo_restore(cairo); } void MonMon::request_draw() { if (draw_requested_ || !pipe_) return; draw_requested_ = true; io::write(pipe_, "d", 1); } void MonMon::pipe(Looper*, int, uint8_t event) { if (event & Looper::EV_READ) { char tmp; if (io::read(pipe_, &tmp, 1)) { if (tmp == 'd') { draw_requested_ = false; 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 lock(mutex_); std::vector dirty; draw_content(cairo_.get(), layout_.get(), w_, h_, dirty); if (dirty.empty()) return; cairo_surface_flush(surface_.get()); for (auto const& r : dirty) { xcb_copy_area(wnd_.conn(), pixmap_.get(), wnd_.get(), gcontext_.get(), r.x, r.y, r.x, r.y, r.width, r.height); } } 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); }