// Test-only methods, restricted to a private headless compositor. #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } class hotplug_test_t : public wf::plugin_interface_t { wf::shared_data::ref_ptr_t repo; static wf::json_t reply(const char *key, const std::string& value) { wf::json_t result; result[key] = value; return result; } static std::string name(const wf::json_t& data) { auto value = std::string(data["name"]); if (value != "HEADLESS-1" && value != "HEADLESS-2" && value != "HEADLESS-3") { throw std::runtime_error("Only fixture output names are allowed"); } return value; } public: void init() override { const char *backend = std::getenv("WLR_BACKENDS"); const char *runtime = std::getenv("XDG_RUNTIME_DIR"); if (!backend || std::string(backend) != "headless" || !runtime || std::string(runtime).find("/tmp/wayfire-crash-repro-") != 0) { throw std::runtime_error("Refusing non-private hotplug test"); } repo->register_method("hotplug-test/remove", [] (wf::json_t data) { auto wanted = name(data); for (auto& [handle, state] : wf::get_core().output_layout->get_current_configuration()) { if (handle->name == wanted && wlr_output_is_headless(handle)) { wlr_output_destroy(handle); return reply("removed", wanted); } } return reply("error", "No matching headless output"); }); repo->register_method("hotplug-test/add", [] (wf::json_t data) { auto wanted = name(data); for (auto& [handle, state] : wf::get_core().output_layout->get_current_configuration()) { if (handle->name == wanted) { return reply("error", "Output already exists"); } } auto& core = wf::get_core(); // Rename before startup so listeners see the original connector name. auto backend = wlr_headless_backend_create(core.ev_loop); auto output = wlr_headless_add_output(backend, 640, 480); wlr_output_set_name(output, wanted.c_str()); if (!wlr_multi_backend_add(core.backend, backend) || !wlr_backend_start(backend)) { wlr_backend_destroy(backend); return reply("error", "Cannot add test backend"); } return reply("added", wanted); }); repo->register_method("hotplug-test/state", [] (wf::json_t) { wf::json_t result = wf::json_t::array(); for (auto view : wf::get_core().get_all_views()) { wf::json_t state; state["id"] = view->get_id(); state["forced"] = bool(view->get_transformed_node()->get_transformer("force-fullscreen")); result.append(state); } return result; }); repo->register_method("hotplug-test/inhibit", [] (wf::json_t data) { for (auto output : wf::get_core().output_layout->get_outputs()) { output->set_inhibited(bool(data["inhibited"])); } return reply("status", "ok"); }); } void fini() override { for (auto method : {"remove", "add", "state", "inhibit"}) { repo->unregister_method(std::string("hotplug-test/") + method); } } }; DECLARE_WAYFIRE_PLUGIN(hotplug_test_t);