commit 91ab6108215759013b0387ae53ef26219ecb2cc0 Author: alan (NyxTrail) Date: Fri Jun 14 08:57:22 2024 +0000 New upstream version 0.3.10 diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90314ef --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +ColumnLimit: 180 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentWidth: 4 +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never + +AllowShortEnumsOnASingleLine: false + +BraceWrapping: + AfterEnum: false + +AlignConsecutiveDeclarations: AcrossEmptyLines + +NamespaceIndentation: All diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000..5b0fe72 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,21 @@ +name: Build + +on: [push, pull_request, workflow_dispatch] +jobs: + nix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: cachix/install-nix-action@v25 + - uses: DeterminateSystems/magic-nix-cache-action@main + + # not needed (yet) + # - uses: cachix/cachix-action@v12 + # with: + # name: hyprland + # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Build + run: nix build -L + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8423e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +build/ +result* +/.vscode/ +/.idea/ +.envrc +.cache + +*.o +*-protocol.c +*-protocol.h +.ccls-cache +*.so + +hyprctl/hyprctl + +gmon.out +*.out +*.tar.gz + +PKGBUILD + +src/version.h + +.direnv diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..871a357 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.19) + +set(VERSION 0.3.10) + +project(hyprwayland-scanner + DESCRIPTION "A hyprland version of wayland-scanner in and for C++" + VERSION ${VERSION} +) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(PREFIX ${CMAKE_INSTALL_PREFIX}) + +set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") + +if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) + message(STATUS "Configuring hyprwayland-scanner in Debug with CMake") + add_compile_definitions(HYPRLAND_DEBUG) +else() + add_compile_options(-O3) + message(STATUS "Configuring hyprwayland-scanner in Release with CMake") +endif() + + +# configure +set(CMAKE_CXX_STANDARD 23) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value + -Wno-missing-field-initializers -Wno-narrowing) + +add_compile_definitions(SCANNER_VERSION="${VERSION}") + +configure_file(hyprwayland-scanner.pc.in hyprwayland-scanner.pc @ONLY) + +# dependencies +message(STATUS "Checking deps...") + +find_package(Threads REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET pugixml) + +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") +add_executable(hyprwayland-scanner ${SRCFILES}) +target_link_libraries(hyprwayland-scanner PRIVATE rt Threads::Threads PkgConfig::deps) + +configure_package_config_file( + hyprwayland-scanner-config.cmake.in + hyprwayland-scanner-config.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/hyprwayland-scanner" + PATH_VARS CMAKE_INSTALL_BINDIR +) +write_basic_package_version_file( + "hyprwayland-scanner-config-version.cmake" + VERSION "${VERSION}" + COMPATIBILITY AnyNewerVersion +) + +# Installation +install(TARGETS hyprwayland-scanner) +install(FILES ${CMAKE_BINARY_DIR}/hyprwayland-scanner.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +install(FILES + ${CMAKE_BINARY_DIR}/hyprwayland-scanner-config.cmake + ${CMAKE_BINARY_DIR}/hyprwayland-scanner-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hyprwayland-scanner +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d405623 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, Hypr Development + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c04269 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# hyprwayland-scanner +A Hyprland implementation of wayland-scanner, in and for C++. + +## Usage + +```sh +hyprwayland-scanner '/path/to/proto' '/path/to/output/directory' +``` + +## Dependencies + +Requires a compiler with C++23 support. + +Dep list: + - pugixml + +## Building + +```sh +cmake -DCMAKE_INSTALL_PREFIX=/usr -B build +cmake --build build -j `nproc` +``` + +### Installation + +```sh +sudo cmake --install build +``` + +## TODO + + - [ ] Support for generating client headers \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6dd9753 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1713537308, + "narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..db0175c --- /dev/null +++ b/flake.nix @@ -0,0 +1,44 @@ +{ + description = "A Hyprland version of wayland-scanner in and for C++"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default-linux"; + }; + + outputs = { + self, + nixpkgs, + systems, + }: let + inherit (nixpkgs) lib; + eachSystem = lib.genAttrs (import systems); + pkgsFor = eachSystem (system: + import nixpkgs { + localSystem.system = system; + overlays = with self.overlays; [hyprwayland-scanner]; + }); + mkDate = longDate: (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); + in { + overlays = { + default = self.overlays.hyprwayland-scanner; + hyprwayland-scanner = final: prev: { + hyprwayland-scanner = final.callPackage ./nix/default.nix { + stdenv = final.gcc13Stdenv; + version = "0.1.0" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + }; + }; + }; + + packages = eachSystem (system: { + default = self.packages.${system}.hyprwayland-scanner; + inherit (pkgsFor.${system}) hyprwayland-scanner; + }); + + formatter = eachSystem (system: pkgsFor.${system}.alejandra); + }; +} diff --git a/hyprwayland-scanner-config.cmake.in b/hyprwayland-scanner-config.cmake.in new file mode 100644 index 0000000..3f086c7 --- /dev/null +++ b/hyprwayland-scanner-config.cmake.in @@ -0,0 +1,16 @@ +@PACKAGE_INIT@ + +set_and_check(BINDIR "@PACKAGE_CMAKE_INSTALL_BINDIR@") + +function(hyprwayland_protocol targets protoName protoPath outputPath) + add_custom_command( + OUTPUT "${outputPath}/${protoName}.cpp" + COMMAND "${BINDIR}/hyprwayland-scanner" "${protoPath}/${protoName}.xml" "${outputPath}" + ) + foreach(target ${targets}) + target_sources(${target} PRIVATE "${outputPath}/${protoName}.cpp") + target_sources(${target} PRIVATE "${outputPath}/${protoName}.hpp") + endforeach() +endfunction() + +check_required_components(hyprwayland-scanner) diff --git a/hyprwayland-scanner.pc.in b/hyprwayland-scanner.pc.in new file mode 100644 index 0000000..2d2b0fe --- /dev/null +++ b/hyprwayland-scanner.pc.in @@ -0,0 +1,6 @@ +hyprwayland_scanner=@PREFIX@/bin/hyprwayland-scanner + +Name: HyprWayland Scanner +URL: https://github.com/hyprwm/hyprwayland-scanner +Description: A Hyprland version of wayland-scanner in and for C++ +Version: @VERSION@ diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..1488c7f --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,27 @@ +{ + lib, + stdenv, + cmake, + pkg-config, + pugixml, + version ? "git", + doCheck ? false, +}: +stdenv.mkDerivation { + pname = "hyprwayland-scanner"; + inherit version doCheck; + src = ../.; + + nativeBuildInputs = [ + cmake + pkg-config + pugixml + ]; + + meta = with lib; { + homepage = "https://github.com/hyprwm/hyprwayland-scanner"; + description = "A Hyprland version of wayland-scanner in and for C++"; + license = licenses.bsd3; + platforms = platforms.linux; + }; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3872e7d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,896 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool waylandEnums = false; + +struct SRequestArgument { + std::string wlType; + std::string interface; + std::string enumName; + std::string name; + bool allowNull = false; +}; + +struct SRequest { + std::vector args; + std::string name; + std::string since; +}; + +struct SEvent { + std::vector args; + std::string name; + std::string since; +}; + +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; + +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; + } + + // iface + if (!arg.interface.empty() && event) { + for (auto& i : XMLDATA.ifaces) { + if (i.name == arg.interface) + return camelize("C_" + arg.interface + "*"); + } + return "wl_resource*"; + } + + 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("C_" + arg.interface + "*"); + } + } + return "wl_resource*"; + } + 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")) { + SRequest srq; + srq.name = rq.attribute("name").as_string(); + srq.since = rq.attribute("since").as_string(); + + for (auto& arg : rq.children("arg")) { + SRequestArgument sargm; + 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")) { + SEvent sev; + sev.name = ev.attribute("name").as_string(); + sev.since = ev.attribute("since").as_string(); + + 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 += R"#(#pragma once + +#include +#include +#include +#include + +#define F std::function + +struct wl_client; +struct 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("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("C_" + arg.interface)); + } + } + } + + for (auto& rq : iface.events) { + for (auto& arg : rq.args) { + if (!arg.interface.empty()) { + HEADER += std::format("\nclass {};", camelize("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("C_" + iface.name); + + // begin the class + HEADER += std::format(R"#( +class {} {{ + public: + {}(wl_client* client, uint32_t version, uint32_t id); + ~{}(); + + // 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, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL, IFACE_CLASS_NAME_CAMEL); + + // add all setters for requests + HEADER += "\n // --------------- Requests --------------- //\n\n"; + + for (auto& rq : iface.requests) { + + std::string args = ", "; + for (auto& arg : rq.args) { + 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 : iface.events) { + std::string args = ""; + for (auto& arg : ev.args) { + args += WPTypeToCType(arg, true) + ", "; + } + + if (!args.empty()) { + args.pop_back(); + args.pop_back(); + } + + HEADER += std::format(" void {}({});\n", camelize("send_" + ev.name), args); + } + + // dangerous ones + for (auto& ev : iface.events) { + std::string args = ""; + for (auto& arg : ev.args) { + 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 : iface.requests) { + + std::string args = ", "; + for (auto& arg : rq.args) { + 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 + HEADER += std::format(R"#( + void onDestroyCalled(); + + F onDestroy; + + wl_resource* pResource = nullptr; + + wl_listener resourceDestroyListener; + + void* pData = nullptr;)#", + IFACE_CLASS_NAME_CAMEL); + + 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("C_" + iface.name); + + // create handlers + for (auto& rq : iface.requests) { + const auto REQUEST_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name); + + std::string argsC = ", "; + for (auto& arg : rq.args) { + 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(); + } + + 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); + } + + // destroy handler + SOURCE += std::format(R"#( +static void _{}__DestroyListener(wl_listener* l, void* d) {{ + {}* pResource = wl_container_of(l, pResource, resourceDestroyListener); + pResource->onDestroyCalled(); +}} +)#", + 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 : iface.requests) { + const auto REQUEST_NAME = camelize(std::string{"_"} + "C_" + IFACE_NAME + "_" + rq.name); + SOURCE += std::format(" (void*){},\n", REQUEST_NAME); + } + + SOURCE += "};\n"; + + // create events + + int evid = 0; + for (auto& ev : iface.events) { + const auto EVENT_NAME = camelize("send_" + ev.name); + + std::string argsC = ""; + for (auto& arg : ev.args) { + argsC += WPTypeToCType(arg, true) + " " + arg.name + ", "; + } + + if (!argsC.empty()) { + argsC.pop_back(); + argsC.pop_back(); + } + + std::string argsN = ", "; + for (auto& arg : ev.args) { + if (!WPTypeToCType(arg, true).starts_with("C")) + argsN += arg.name + ", "; + else + argsN += arg.name + "->pResource, "; + } + + 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++; + } + + // dangerous + evid = 0; + for (auto& ev : iface.events) { + const auto EVENT_NAME = camelize("send_" + ev.name + "_raw"); + + std::string argsC = ""; + for (auto& arg : ev.args) { + argsC += WPTypeToCType(arg, true, true) + " " + arg.name + ", "; + } + + if (!argsC.empty()) { + argsC.pop_back(); + argsC.pop_back(); + } + + std::string argsN = ", "; + for (auto& arg : ev.args) { + 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 (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(" {{ \"{}\", \"{}\", {}}},\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(" {{ \"{}\", \"{}\", {}}},\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 {} = {{ + "{}", {}, + {}, {}, + {}, {}, +}}; +)#", + 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 + 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.link); + resourceDestroyListener.notify = _{}__DestroyListener; + wl_resource_add_destroy_listener(pResource, &resourceDestroyListener); + + wl_resource_set_implementation(pResource, {}, this, nullptr); +}} + +{}::~{}() {{ + wl_list_remove(&resourceDestroyListener.link); + wl_list_init(&resourceDestroyListener.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.link); + wl_list_init(&resourceDestroyListener.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); + + for (auto& rq : 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 == "--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; +} \ No newline at end of file