#include "common.hh" #include #include #include #include #include #include "animator.hh" #include "args.hh" #include "io.hh" #include "icecc.hh" #include "monmon.hh" #include "poll_looper.hh" #include "x.hh" namespace { 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; } #if FAKE_MONITOR case ' ': if (monmon->match(e->event)) { monmon->toggle_fakes(); } break; #endif } 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 (XCB_EVENT_SENT(e) && 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("columns", "COLUMNS", "force columns to be COLUMNS instead of" " calculated from window size"); 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; } unsigned columns = 0; { auto tmp = args->arg("columns", nullptr); if (tmp) { char* end = nullptr; errno = 0; columns = strtoul(tmp, &end, 10); if (errno || columns == 0 || *end) { std::cerr << "Invalid value for columns: '" << tmp << "'" << std::endl; 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"); MonMon::preload(atoms.get()); std::shared_ptr ewmh(x::Ewmh::create(conn, screen_index)); std::shared_ptr monmon(create_icecc_monmon(looper, columns)); monmon->init(conn, screen, format, atoms, ewmh, 400, 400, args->is_set("titlebar"), args->is_set("black")); if (!monmon->connect(args.get())) return EXIT_FAILURE; std::thread xcb_thread(xcb_event_loop, conn, atoms, monmon); conn.reset(); looper->run(); xcb_thread.join(); return EXIT_SUCCESS; }