#include #include #include #include #include #include #include #include #include bool waylandEnums = false; bool clientCode = false; bool noInterfaces = false; struct SRequestArgument { std::string wlType; std::string interface; std::string enumName; std::string name; bool newType = false; bool allowNull = false; }; struct SWaylandFunction { std::vector args; std::string name; std::string since; std::string newIdType = ""; // client only bool destructor = false; }; struct SInterface { std::vector requests; std::vector events; std::string name; int version = 1; }; struct SEnum { std::string name; std::string nameOriginal; std::vector> values; }; struct { std::vector ifaces; std::vector enums; } XMLDATA; const char* resourceName() { return clientCode ? "wl_proxy" : "wl_resource"; } std::string sanitize(const std::string& in) { if (in == "namespace") return "namespace_"; if (in == "class") return "class_"; if (in == "delete") return "delete_"; if (in == "new") return "new_"; return in; } std::string argsToShort(std::vector& args, const std::string& since) { std::string shortt = since; for (auto& a : args) { if (a.wlType == "int") shortt += "i"; else if (a.wlType == "new_id") { if (a.interface.empty()) shortt += "su"; shortt += "n"; } else if (a.wlType == "uint") shortt += "u"; else if (a.wlType == "fixed") shortt += "f"; else if (a.wlType == "string") shortt += std::string(a.allowNull ? "?s" : "s"); else if (a.wlType == "object") shortt += std::string(a.allowNull ? "?" : "") + "o"; else if (a.wlType == "array") shortt += "a"; else if (a.wlType == "fd") shortt += "h"; else throw std::runtime_error("Unknown arg in argsToShort"); } return shortt; } std::string camelize(std::string snake) { std::string result = ""; for (size_t i = 0; i < snake.length(); ++i) { if (snake[i] == '_' && i != 0 && i + 1 < snake.length() && snake[i + 1] != '_') { result += ::toupper(snake[i + 1]); i++; continue; } result += snake[i]; } return result; } std::string WPTypeToCType(const SRequestArgument& arg, bool event /* events pass iface ptrs, requests ids */, bool ignoreTypes = false /* for dangerous */) { if (arg.wlType == "uint" || arg.wlType == "new_id") { if (arg.enumName.empty() && arg.interface.empty()) return "uint32_t"; // enum if (!arg.enumName.empty()) { for (auto& e : XMLDATA.enums) { if (e.nameOriginal == arg.enumName) return e.name; } return "uint32_t"; } if (!event && clientCode && arg.wlType == "new_id") return "wl_proxy*"; // iface if (!arg.interface.empty() && event) { for (auto& i : XMLDATA.ifaces) { if (i.name == arg.interface) return camelize((clientCode ? "CC_" : "C_") + arg.interface + "*"); } return std::string{resourceName()} + "*"; } return "uint32_t"; } if (arg.wlType == "object") { if (!arg.interface.empty() && event && !ignoreTypes) { for (auto& i : XMLDATA.ifaces) { if (i.name == arg.interface) return camelize((clientCode ? "CC_" : "C_") + arg.interface + "*"); } } return std::string{resourceName()} + "*"; } if (arg.wlType == "int" || arg.wlType == "fd") return "int32_t"; if (arg.wlType == "fixed") return "wl_fixed_t"; if (arg.wlType == "array") return "wl_array*"; if (arg.wlType == "string") return "const char*"; throw std::runtime_error("unknown wp type"); return ""; } std::string HEADER; std::string SOURCE; struct { std::string name; std::string nameOriginal; std::string fileName; } PROTO_DATA; void parseXML(pugi::xml_document& doc) { for (auto& ge : doc.child("protocol").children("enum")) { SEnum enum_; enum_.nameOriginal = ge.attribute("name").as_string(); enum_.name = waylandEnums ? "enum " + PROTO_DATA.name + "_" + enum_.nameOriginal : camelize(PROTO_DATA.name + "_" + enum_.nameOriginal); for (auto& entry : ge.children("entry")) { auto VALUENAME = enum_.nameOriginal + "_" + entry.attribute("name").as_string(); std::transform(VALUENAME.begin(), VALUENAME.end(), VALUENAME.begin(), ::toupper); enum_.values.emplace_back(std::make_pair<>(VALUENAME, entry.attribute("value").as_int())); } XMLDATA.enums.push_back(enum_); } for (auto& iface : doc.child("protocol").children("interface")) { SInterface ifc; ifc.name = iface.attribute("name").as_string(); ifc.version = iface.attribute("version").as_int(); for (auto& en : iface.children("enum")) { SEnum enum_; enum_.nameOriginal = en.attribute("name").as_string(); enum_.name = waylandEnums ? "enum " + ifc.name + "_" + enum_.nameOriginal : camelize(ifc.name + "_" + enum_.nameOriginal); for (auto& entry : en.children("entry")) { auto VALUENAME = ifc.name + "_" + enum_.nameOriginal + "_" + entry.attribute("name").as_string(); std::transform(VALUENAME.begin(), VALUENAME.end(), VALUENAME.begin(), ::toupper); enum_.values.emplace_back(std::make_pair<>(VALUENAME, entry.attribute("value").as_int())); } XMLDATA.enums.push_back(enum_); } for (auto& rq : iface.children("request")) { SWaylandFunction srq; srq.name = rq.attribute("name").as_string(); srq.since = rq.attribute("since").as_string(); srq.destructor = rq.attribute("type").as_string() == std::string{"destructor"}; for (auto& arg : rq.children("arg")) { SRequestArgument sargm; if (arg.attribute("type").as_string() == std::string{"new_id"} && clientCode) srq.newIdType = arg.attribute("interface").as_string(); sargm.newType = arg.attribute("type").as_string() == std::string{"new_id"} && clientCode; sargm.name = sanitize(arg.attribute("name").as_string()); sargm.wlType = arg.attribute("type").as_string(); sargm.interface = arg.attribute("interface").as_string(); sargm.enumName = arg.attribute("enum").as_string(); sargm.allowNull = arg.attribute("allow-null").as_string() == std::string{"true"}; srq.args.push_back(sargm); } ifc.requests.push_back(srq); } for (auto& ev : iface.children("event")) { SWaylandFunction sev; sev.name = ev.attribute("name").as_string(); sev.since = ev.attribute("since").as_string(); sev.destructor = ev.attribute("type").as_string() == std::string{"destructor"}; for (auto& arg : ev.children("arg")) { SRequestArgument sargm; sargm.name = sanitize(arg.attribute("name").as_string()); sargm.interface = arg.attribute("interface").as_string(); sargm.wlType = arg.attribute("type").as_string(); sargm.enumName = arg.attribute("enum").as_string(); sargm.allowNull = arg.attribute("allow-null").as_string() == std::string{"true"}; sev.args.push_back(sargm); } ifc.events.push_back(sev); } XMLDATA.ifaces.push_back(ifc); } } void parseHeader() { // add some boilerplate HEADER += std::format(R"#(#pragma once #include #include #include {} #define F std::function {} )#", (clientCode ? "#include " : "#include "), (clientCode ? "struct wl_proxy;" : "struct wl_client;\nstruct wl_resource;")); // parse all enums if (!waylandEnums) { for (auto& en : XMLDATA.enums) { HEADER += std::format("enum {} : uint32_t {{\n", en.name); for (auto& [k, v] : en.values) { HEADER += std::format(" {} = {},\n", k, v); } HEADER += "};\n\n"; } } // fw declare all classes for (auto& iface : XMLDATA.ifaces) { const auto IFACE_CLASS_NAME_CAMEL = camelize((clientCode ? "CC_" : "C_") + iface.name); HEADER += std::format("\nclass {};", IFACE_CLASS_NAME_CAMEL); for (auto& rq : iface.requests) { for (auto& arg : rq.args) { if (!arg.interface.empty()) { HEADER += std::format("\nclass {};", camelize((clientCode ? "CC_" : "C_") + arg.interface)); } } } for (auto& rq : iface.events) { for (auto& arg : rq.args) { if (!arg.interface.empty()) { HEADER += std::format("\nclass {};", camelize((clientCode ? "CC_" : "C_") + arg.interface)); } } } } HEADER += "\n\n#ifndef HYPRWAYLAND_SCANNER_NO_INTERFACES\n"; for (auto& iface : XMLDATA.ifaces) { const auto IFACE_WL_NAME = iface.name + "_interface"; const auto IFACE_WL_NAME_CAMEL = camelize(iface.name + "_interface"); HEADER += std::format("extern const wl_interface {};\n", IFACE_WL_NAME, IFACE_WL_NAME_CAMEL, IFACE_WL_NAME); } HEADER += "\n#endif\n"; for (auto& iface : XMLDATA.ifaces) { const auto IFACE_NAME_CAMEL = camelize(iface.name); const auto IFACE_CLASS_NAME_CAMEL = camelize((clientCode ? "CC_" : "C_") + iface.name); if (!clientCode) { HEADER += std::format(R"#( struct {}DestroyWrapper {{ wl_listener listener; {}* parent = nullptr; }}; )#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL); } // begin the class HEADER += std::format(R"#( class {} {{ public: {}({}); ~{}(); )#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, (clientCode ? "wl_proxy*" : "wl_client* client, uint32_t version, uint32_t id"), IFACE_CLASS_NAME_CAMEL); if (!clientCode) { HEADER += std::format(R"#( // set a listener for when this resource is _being_ destroyed void setOnDestroy(F handler) {{ onDestroy = handler; }} // set the data for this resource void setData(void* data) {{ pData = data; }} // get the data for this resource void* data() {{ return pData; }} // get the raw wl_resource ptr wl_resource* resource() {{ return pResource; }} // get the client wl_client* client() {{ return wl_resource_get_client(pResource); }} // send an error void error(uint32_t error, const std::string& message) {{ wl_resource_post_error(pResource, error, "%s", message.c_str()); }} // send out of memory void noMemory() {{ wl_resource_post_no_memory(pResource); }} // get the resource version int version() {{ return wl_resource_get_version(pResource); }} )#", IFACE_CLASS_NAME_CAMEL); } else { HEADER += R"#( // set the data for this resource void setData(void* data) {{ pData = data; }} // get the data for this resource void* data() {{ return pData; }} // get the raw wl_resource (wl_proxy) ptr wl_proxy* resource() {{ return pResource; }} // get the raw wl_proxy ptr wl_proxy* proxy() {{ return pResource; }} // get the resource version int version() {{ return wl_proxy_get_version(pResource); }} )#"; } // add all setters for requests HEADER += "\n // --------------- Requests --------------- //\n\n"; for (auto& rq : (clientCode ? iface.events : iface.requests)) { std::string args = ", "; for (auto& arg : rq.args) { if (arg.newType) continue; args += WPTypeToCType(arg, false) + ", "; } args.pop_back(); args.pop_back(); HEADER += std::format(" void {}(F handler);\n", camelize("set_" + rq.name), IFACE_CLASS_NAME_CAMEL, args); } // start events HEADER += "\n // --------------- Events --------------- //\n\n"; for (auto& ev : (!clientCode ? iface.events : iface.requests)) { std::string args = ""; for (auto& arg : ev.args) { if (arg.newType) continue; args += WPTypeToCType(arg, true) + ", "; } if (!args.empty()) { args.pop_back(); args.pop_back(); } HEADER += std::format(" {} {}({});\n", ev.newIdType.empty() ? "void" : "wl_proxy*", camelize("send_" + ev.name), args); } // dangerous ones if (!clientCode) { for (auto& ev : (!clientCode ? iface.events : iface.requests)) { std::string args = ""; for (auto& arg : ev.args) { if (arg.newType) continue; args += WPTypeToCType(arg, true, true) + ", "; } if (!args.empty()) { args.pop_back(); args.pop_back(); } HEADER += std::format(" void {}({});\n", camelize("send_" + ev.name + "_raw"), args); } } // end events // start private section HEADER += "\n private:\n"; // start requests storage HEADER += " struct {\n"; for (auto& rq : (clientCode ? iface.events : iface.requests)) { std::string args = ", "; for (auto& arg : rq.args) { if (arg.newType) continue; args += WPTypeToCType(arg, false) + ", "; } if (!args.empty()) { args.pop_back(); args.pop_back(); } HEADER += std::format(" F {};\n", IFACE_CLASS_NAME_CAMEL, args, camelize(rq.name)); } // end requests storage HEADER += " } requests;\n"; // constant resource stuff if (!clientCode) { HEADER += std::format(R"#( void onDestroyCalled(); F onDestroy; wl_resource* pResource = nullptr; {}DestroyWrapper resourceDestroyListener; void* pData = nullptr;)#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL); } else { HEADER += R"#( wl_proxy* pResource = nullptr; bool destroyed = false; void* pData = nullptr;)#"; } HEADER += "\n};\n\n"; } HEADER += "\n\n#undef F\n"; } void parseSource() { SOURCE += std::format(R"#(#define private public #define HYPRWAYLAND_SCANNER_NO_INTERFACES #include "{}.hpp" #undef private #define F std::function )#", PROTO_DATA.fileName); // reference interfaces // dummy SOURCE += R"#( static const wl_interface* dummyTypes[] = { nullptr }; )#"; SOURCE += R"#( // Reference all other interfaces. // The reason why this is in snake is to // be able to cooperate with existing // wayland_scanner interfaces (they are interop) )#"; std::vector declaredIfaces; for (auto& iface : XMLDATA.ifaces) { const auto IFACE_WL_NAME = iface.name + "_interface"; const auto IFACE_WL_NAME_CAMEL = camelize(iface.name + "_interface"); declaredIfaces.push_back(IFACE_WL_NAME); SOURCE += std::format("extern const wl_interface {};\n", IFACE_WL_NAME, IFACE_WL_NAME_CAMEL, IFACE_WL_NAME); } for (auto& iface : XMLDATA.ifaces) { // do all referenced too for (auto& rq : iface.requests) { for (auto& arg : rq.args) { if (arg.interface.empty()) continue; const auto IFACE_WL_NAME2 = arg.interface + "_interface"; const auto IFACE_WL_NAME_CAMEL2 = camelize(arg.interface + "_interface"); if (std::find(declaredIfaces.begin(), declaredIfaces.end(), IFACE_WL_NAME2) == declaredIfaces.end()) { SOURCE += std::format("extern const wl_interface {};\n", IFACE_WL_NAME2, IFACE_WL_NAME_CAMEL2, IFACE_WL_NAME2); declaredIfaces.push_back(IFACE_WL_NAME2); } } } for (auto& ev : iface.events) { for (auto& arg : ev.args) { if (arg.interface.empty()) continue; const auto IFACE_WL_NAME2 = arg.interface + "_interface"; const auto IFACE_WL_NAME_CAMEL2 = camelize(arg.interface + "_interface"); if (std::find(declaredIfaces.begin(), declaredIfaces.end(), IFACE_WL_NAME2) == declaredIfaces.end()) { SOURCE += std::format("extern const wl_interface {};\n", IFACE_WL_NAME2, IFACE_WL_NAME_CAMEL2, IFACE_WL_NAME2); declaredIfaces.push_back(IFACE_WL_NAME2); } } } } // declare ifaces for (auto& iface : XMLDATA.ifaces) { const auto IFACE_WL_NAME = iface.name + "_interface"; const auto IFACE_NAME = iface.name; const auto IFACE_NAME_CAMEL = camelize(iface.name); const auto IFACE_CLASS_NAME_CAMEL = camelize((clientCode ? "CC_" : "C_") + iface.name); // create handlers for (auto& rq : (clientCode ? iface.events : iface.requests)) { const auto REQUEST_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name); std::string argsC = ", "; for (auto& arg : rq.args) { if (arg.newType) continue; argsC += WPTypeToCType(arg, false) + " " + arg.name + ", "; } argsC.pop_back(); argsC.pop_back(); std::string argsN = ", "; for (auto& arg : rq.args) { argsN += arg.name + ", "; } if (!argsN.empty()) { argsN.pop_back(); argsN.pop_back(); } if (!clientCode) { SOURCE += std::format(R"#( static void {}(wl_client* client, wl_resource* resource{}) {{ const auto PO = ({}*)wl_resource_get_user_data(resource); if (PO && PO->requests.{}) PO->requests.{}(PO{}); }} )#", REQUEST_NAME, argsC, IFACE_CLASS_NAME_CAMEL, camelize(rq.name), camelize(rq.name), argsN); } else { SOURCE += std::format(R"#( static void {}(void* data, void* resource{}) {{ const auto PO = ({}*)data; if (PO && PO->requests.{}) PO->requests.{}(PO{}); }} )#", REQUEST_NAME, argsC, IFACE_CLASS_NAME_CAMEL, camelize(rq.name), camelize(rq.name), argsN); } } // destroy handler if (!clientCode) { SOURCE += std::format(R"#( static void _{}__DestroyListener(wl_listener* l, void* d) {{ {}DestroyWrapper *wrap = wl_container_of(l, wrap, listener); {}* pResource = wrap->parent; pResource->onDestroyCalled(); }} )#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL); } // create vtable const auto IFACE_VTABLE_NAME = "_" + IFACE_CLASS_NAME_CAMEL + "VTable"; SOURCE += std::format(R"#( static const void* {}[] = {{ )#", IFACE_VTABLE_NAME); for (auto& rq : (clientCode ? iface.events : iface.requests)) { const auto REQUEST_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name); SOURCE += std::format(" (void*){},\n", REQUEST_NAME); } if ((clientCode ? iface.events : iface.requests).empty()) { SOURCE += " nullptr,\n"; } SOURCE += "};\n"; // create events int evid = 0; for (auto& ev : (!clientCode ? iface.events : iface.requests)) { const auto EVENT_NAME = camelize("send_" + ev.name); std::string argsC = ""; for (auto& arg : ev.args) { if (arg.newType) continue; argsC += WPTypeToCType(arg, true) + " " + arg.name + ", "; } if (!argsC.empty()) { argsC.pop_back(); argsC.pop_back(); } std::string argsN = ", "; for (auto& arg : ev.args) { if (arg.newType) argsN += "nullptr, "; else if (!WPTypeToCType(arg, true).starts_with("C")) argsN += arg.name + ", "; else argsN += (arg.name + " ? " + arg.name + "->pResource : nullptr, "); } argsN.pop_back(); argsN.pop_back(); if (!clientCode) { SOURCE += std::format(R"#( void {}::{}({}) {{ if (!pResource) return; wl_resource_post_event(pResource, {}{}); }} )#", IFACE_CLASS_NAME_CAMEL, EVENT_NAME, argsC, evid, argsN); } else { std::string retType = ev.newIdType.empty() ? "void" : "wl_proxy"; std::string ptrRetType = ev.newIdType.empty() ? "void" : "wl_proxy*"; std::string flags = ev.destructor ? "1" : "0"; SOURCE += std::format(R"#( {} {}::{}({}) {{ if (!pResource) return{};{} auto proxy = wl_proxy_marshal_flags(pResource, {}, {}, wl_proxy_get_version(pResource), {}{});{} }} )#", ptrRetType, IFACE_CLASS_NAME_CAMEL, EVENT_NAME, argsC, (ev.newIdType.empty() ? "" : " nullptr"), (ev.destructor ? "\n destroyed = true;" : ""), evid, (ev.newIdType.empty() ? "nullptr" : "&" + ev.newIdType + "_interface"), flags, argsN, (ev.newIdType.empty() ? "\n proxy;" : "\n\n return proxy;")); } evid++; } // dangerous if (!clientCode) { evid = 0; for (auto& ev : iface.events) { const auto EVENT_NAME = camelize("send_" + ev.name + "_raw"); std::string argsC = ""; for (auto& arg : ev.args) { if (arg.newType) continue; argsC += WPTypeToCType(arg, true, true) + " " + arg.name + ", "; } if (!argsC.empty()) { argsC.pop_back(); argsC.pop_back(); } std::string argsN = ", "; for (auto& arg : ev.args) { if (arg.newType) continue; argsN += arg.name + ", "; } argsN.pop_back(); argsN.pop_back(); SOURCE += std::format(R"#( void {}::{}({}) {{ if (!pResource) return; wl_resource_post_event(pResource, {}{}); }} )#", IFACE_CLASS_NAME_CAMEL, EVENT_NAME, argsC, evid, argsN); evid++; } } // wayland interfaces and stuff // type tables for (auto& rq : iface.requests) { if (rq.args.empty()) continue; const auto TYPE_TABLE_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name + "_types"); SOURCE += std::format("static const wl_interface* {}[] = {{\n", TYPE_TABLE_NAME); for (auto& arg : rq.args) { if (arg.interface.empty()) { SOURCE += " nullptr,\n"; continue; } SOURCE += std::format(" &{}_interface,\n", arg.interface); } SOURCE += "};\n"; } for (auto& ev : iface.events) { if (ev.args.empty()) continue; const auto TYPE_TABLE_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + ev.name + "_types"); SOURCE += std::format("static const wl_interface* {}[] = {{\n", TYPE_TABLE_NAME); for (auto& arg : ev.args) { if (arg.interface.empty()) { SOURCE += " nullptr,\n"; continue; } SOURCE += std::format(" &{}_interface,\n", arg.interface); } SOURCE += "};\n"; } const auto MESSAGE_NAME_REQUESTS = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_requests"); const auto MESSAGE_NAME_EVENTS = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_events"); // message if (!noInterfaces) { if (iface.requests.size() > 0) { SOURCE += std::format(R"#( static const wl_message {}[] = {{ )#", MESSAGE_NAME_REQUESTS); for (auto& rq : iface.requests) { // create type table const auto TYPE_TABLE_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name + "_types"); SOURCE += std::format(" {{ .name = \"{}\", .signature = \"{}\", .types = {}}},\n", rq.name, argsToShort(rq.args, rq.since), rq.args.empty() ? "dummyTypes + 0" : TYPE_TABLE_NAME + " + 0"); } SOURCE += "};\n"; } if (iface.events.size() > 0) { SOURCE += std::format(R"#( static const wl_message {}[] = {{ )#", MESSAGE_NAME_EVENTS); for (auto& ev : iface.events) { // create type table const auto TYPE_TABLE_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + ev.name + "_types"); SOURCE += std::format(" {{ .name = \"{}\", .signature = \"{}\", .types = {}}},\n", ev.name, argsToShort(ev.args, ev.since), ev.args.empty() ? "dummyTypes + 0" : TYPE_TABLE_NAME + " + 0"); } SOURCE += "};\n"; } // iface SOURCE += std::format(R"#( const wl_interface {} = {{ .name = "{}", .version = {}, .method_count = {}, .methods = {}, .event_count = {}, .events = {}, }}; )#", IFACE_WL_NAME, iface.name, iface.version, iface.requests.size(), (iface.requests.size() > 0 ? MESSAGE_NAME_REQUESTS : "nullptr"), iface.events.size(), (iface.events.size() > 0 ? MESSAGE_NAME_EVENTS : "nullptr")); } // protocol body if (!clientCode) { SOURCE += std::format(R"#( {}::{}(wl_client* client, uint32_t version, uint32_t id) : pResource(wl_resource_create(client, &{}, version, id)) {{ if (!pResource) return; wl_resource_set_user_data(pResource, this); wl_list_init(&resourceDestroyListener.listener.link); resourceDestroyListener.listener.notify = _{}__DestroyListener; resourceDestroyListener.parent = this; wl_resource_add_destroy_listener(pResource, &resourceDestroyListener.listener); wl_resource_set_implementation(pResource, {}, this, nullptr); }} {}::~{}() {{ wl_list_remove(&resourceDestroyListener.listener.link); wl_list_init(&resourceDestroyListener.listener.link); // if we still own the wayland resource, // it means we need to destroy it. if (pResource && wl_resource_get_user_data(pResource) == this) {{ wl_resource_set_user_data(pResource, nullptr); wl_resource_destroy(pResource); }} }} void {}::onDestroyCalled() {{ wl_resource_set_user_data(pResource, nullptr); wl_list_remove(&resourceDestroyListener.listener.link); wl_list_init(&resourceDestroyListener.listener.link); // set the resource to nullptr, // as it will be freed. If the consumer does not destroy this resource // in onDestroy here, we'd be doing a UAF in the ~dtor pResource = nullptr; if (onDestroy) onDestroy(this); }} )#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, IFACE_NAME + "_interface", IFACE_CLASS_NAME_CAMEL, IFACE_VTABLE_NAME, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL); } else { std::string DTOR_FUNC = ""; for (auto& rq : iface.requests) { if (!rq.destructor) continue; DTOR_FUNC = camelize("send_" + rq.name) + "()"; break; } if (DTOR_FUNC.empty()) DTOR_FUNC = "wl_proxy_destroy(pResource)"; SOURCE += std::format(R"#( {}::{}(wl_proxy* resource) : pResource(resource) {{ if (!pResource) return; wl_proxy_add_listener(pResource, (void (**)(void))&{}, this); }} {}::~{}() {{ if (!destroyed) {}; }} )#", IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, IFACE_VTABLE_NAME, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, DTOR_FUNC); } for (auto& rq : (clientCode ? iface.events : iface.requests)) { std::string args = ", "; for (auto& arg : rq.args) { args += WPTypeToCType(arg, false) + ", "; } args.pop_back(); args.pop_back(); SOURCE += std::format(R"#( void {}::{}(F handler) {{ requests.{} = handler; }} )#", IFACE_CLASS_NAME_CAMEL, camelize("set_" + rq.name), IFACE_CLASS_NAME_CAMEL, args, camelize(rq.name)); } } SOURCE += "\n#undef F\n"; } int main(int argc, char** argv, char** envp) { std::string outpath = ""; std::string protopath = ""; int pathsTaken = 0; for (int i = 1; i < argc; ++i) { std::string curarg = argv[i]; if (curarg == "-v" || curarg == "--version") { std::cout << SCANNER_VERSION << "\n"; return 0; } if (curarg == "-c" || curarg == "--client") { clientCode = true; continue; } if (curarg == "--no-interfaces") { noInterfaces = true; continue; } if (curarg == "--wayland-enums") { waylandEnums = true; continue; } if (pathsTaken == 0) { protopath = curarg; pathsTaken++; continue; } else if (pathsTaken == 1) { outpath = curarg; pathsTaken++; continue; } std::cout << "Too many args or unknown arg " << curarg << "\n"; return 1; } if (outpath.empty() || protopath.empty()) { std::cerr << "Not enough args\n"; return 1; } // build! pugi::xml_document doc; if (!doc.load_file(protopath.c_str())) { std::cerr << "Couldn't load proto\n"; return 1; } PROTO_DATA.nameOriginal = doc.child("protocol").attribute("name").as_string(); PROTO_DATA.name = camelize(PROTO_DATA.nameOriginal); PROTO_DATA.fileName = protopath.substr(protopath.find_last_of('/') + 1, protopath.length() - (protopath.find_last_of('/') + 1) - 4); const auto COPYRIGHT = std::format("// Generated with hyprwayland-scanner {}. Made with vaxry's keyboard and ❤️.\n// {}\n\n/*\n This protocol's authors' copyright notice is:\n\n{}\n*/\n\n", SCANNER_VERSION, PROTO_DATA.nameOriginal, std::string{doc.child("protocol").child("copyright").child_value()}); parseXML(doc); parseHeader(); parseSource(); const auto HPATH = outpath + "/" + PROTO_DATA.fileName + ".hpp"; const auto CPATH = outpath + "/" + PROTO_DATA.fileName + ".cpp"; bool needsToWriteHeader = true, needsToWriteSource = true; if (std::filesystem::exists(HPATH)) { // check if we need to overwrite std::ifstream headerIn(HPATH); std::string content((std::istreambuf_iterator(headerIn)), (std::istreambuf_iterator())); if (content == COPYRIGHT + HEADER) needsToWriteHeader = false; headerIn.close(); } if (std::filesystem::exists(CPATH)) { // check if we need to overwrite std::ifstream sourceIn(CPATH); std::string content((std::istreambuf_iterator(sourceIn)), (std::istreambuf_iterator())); if (content == COPYRIGHT + SOURCE) needsToWriteSource = false; sourceIn.close(); } if (needsToWriteHeader) { std::ofstream header(HPATH, std::ios::trunc); header << COPYRIGHT << HEADER; header.close(); } if (needsToWriteSource) { std::ofstream source(CPATH, std::ios::trunc); source << COPYRIGHT << SOURCE; source.close(); } return 0; }