pax_global_header00006660000000000000000000000064151406242350014514gustar00rootroot0000000000000052 comment=37bc90eed02b0c8b5a77a0b00867baf3005cfb98 hyprwm-hyprwire-37bc90e/000077500000000000000000000000001514062423500153275ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/.clang-format000066400000000000000000000034161514062423500177060ustar00rootroot00000000000000--- 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 hyprwm-hyprwire-37bc90e/.clang-tidy000066400000000000000000000072071514062423500173710ustar00rootroot00000000000000WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' FormatStyle: 'file' Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declaration-namespace, -bugprone-forward-declaration-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, -bugprone-assignment-in-if-condition, concurrency-*, -concurrency-mt-unsafe, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-init-variables, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, google-global-names-in-headers, -google-readability-casting, google-runtime-operator, misc-*, -misc-unused-parameters, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -misc-use-anonymous-namespace, -misc-const-correctness, modernize-*, -modernize-return-braced-init-list, -modernize-use-trailing-return-type, -modernize-use-using, -modernize-use-override, -modernize-avoid-c-arrays, -modernize-macro-to-enum, -modernize-loop-convert, -modernize-use-nodiscard, -modernize-pass-by-value, -modernize-use-auto, performance-*, -performance-avoid-endl, -performance-unnecessary-value-param, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-magic-numbers, -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, -readability-else-after-return, -readability-container-data-pointer, -readability-implicit-bool-conversion, -readability-avoid-nested-conditional-operator, -readability-redundant-member-init, -readability-redundant-string-init, -readability-avoid-const-params-in-decls, -readability-named-parameter, -readability-convert-member-functions-to-static, -readability-qualified-auto, -readability-make-member-function-const, -readability-isolate-declaration, -readability-inconsistent-declaration-parameter-name, -clang-diagnostic-error, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true performance-inefficient-string-concatenation.StrictMode: true readability-braces-around-statements.ShortStatementLines: 0 readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ClassIgnoredRegexp: I.* readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? readability-identifier-naming.EnumCase: CamelCase readability-identifier-naming.EnumPrefix: e readability-identifier-naming.EnumConstantCase: UPPER_CASE readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.NamespaceCase: CamelCase readability-identifier-naming.StructPrefix: S readability-identifier-naming.StructCase: CamelCase hyprwm-hyprwire-37bc90e/.github/000077500000000000000000000000001514062423500166675ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/.github/workflows/000077500000000000000000000000001514062423500207245ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/.github/workflows/nix.yml000066400000000000000000000032561514062423500222530ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: strategy: matrix: package: - hyprwire - hyprwire-with-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: nix_conf: | keep-env-derivations = true keep-outputs = true - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}- # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G # do purge caches purge: true # purge all versions of the cache purge-prefixes: nix-${{ runner.os }}- # created more than this number of seconds ago purge-created: 0 # or, last accessed more than this number of seconds ago # relative to the start of the `Post Restore and save Nix store` phase purge-last-accessed: 0 # except any version with the key that is the same as the `primary-key` purge-primary-key: never # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build run: nix build .#${{ matrix.package }} --print-build-logs hyprwm-hyprwire-37bc90e/.gitignore000066400000000000000000000012171514062423500173200ustar00rootroot00000000000000CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps CMakeUserPresets.json # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #cmake-build-* build/ .vscode/ .cache/ tests/generated/ *.inc src/renderer/gl/shaders/Shaders.hpphyprwm-hyprwire-37bc90e/CMakeLists.txt000066400000000000000000000104001514062423500200620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} HYPRWIRE_VERSION) add_compile_definitions(HYPRWIRE_VERSION="${HYPRWIRE_VERSION}") project( hyprwire VERSION ${HYPRWIRE_VERSION} DESCRIPTION "A fast and consistent wire protocol for IPC") include(CTest) include(CheckIncludeFile) include(GNUInstallDirs) add_subdirectory(scanner) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET hyprutils>=0.9.0 libffi ) configure_file(hyprwire.pc.in hyprwire.pc @ONLY) set(CMAKE_CXX_STANDARD 23) add_compile_options( -Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wpedantic) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hyprwire in Debug") add_compile_definitions(HYPRWIRE_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring hyprwire in Release") endif() add_compile_definitions(HT_HIDDEN=public) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") execute_process(COMMAND ./scripts/generateShaderIncludes.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_library(hyprwire SHARED ${SRCFILES}) target_include_directories( hyprwire PUBLIC "./include" PRIVATE "./src" "${CMAKE_BINARY_DIR}") set_target_properties(hyprwire PROPERTIES VERSION ${HYPRWIRE_VERSION} SOVERSION 3) target_link_libraries(hyprwire PkgConfig::deps) check_include_file("sys/timerfd.h" HAS_TIMERFD) pkg_check_modules(epoll IMPORTED_TARGET epoll-shim) if(NOT HAS_TIMERFD AND epoll_FOUND) target_link_libraries(hyprwire PkgConfig::epoll) endif() # helper func function(protocol proto_dir proto_name is_client out_base out_var) if(is_client) set(mode "--client") set(suffix "client") else() set(mode "") set(suffix "server") endif() set(outdir "${CMAKE_SOURCE_DIR}/tests/generated") set(xml_in "${proto_dir}/${proto_name}.xml") set(tmp_cpp "${outdir}/${proto_name}-${suffix}.cpp") set(tmp_hpp "${outdir}/${proto_name}-${suffix}.hpp") set(out_cpp "${outdir}/${out_base}-${suffix}.cpp") set(out_hpp "${outdir}/${out_base}-${suffix}.hpp") add_custom_command( OUTPUT "${out_cpp}" "${out_hpp}" COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" COMMAND hyprwire-scanner ${mode} "${xml_in}" "${outdir}" DEPENDS hyprwire-scanner "${xml_in}" VERBATIM COMMENT "Generating ${suffix} protocol from ${proto_name}.xml -> ${out_base}-${suffix}.{cpp,hpp}" ) set(${out_var} "${out_cpp};${out_hpp}" PARENT_SCOPE) endfunction() # tests if(BUILD_TESTING) message(STATUS "building tests is enabled") enable_testing() add_custom_target(tests) message(STATUS "generating protocols") protocol("${CMAKE_SOURCE_DIR}/tests" "protocol-v1" TRUE "test_protocol_v1" CLIENT_GEN) protocol("${CMAKE_SOURCE_DIR}/tests" "protocol-v1" FALSE "test_protocol_v1" SERVER_GEN) add_executable(client "${CMAKE_SOURCE_DIR}/tests/Client.cpp" "tests/generated/test_protocol_v1-client.cpp") target_link_libraries(client PRIVATE PkgConfig::deps hyprwire) add_dependencies(tests client) add_executable(server "${CMAKE_SOURCE_DIR}/tests/Server.cpp" "tests/generated/test_protocol_v1-server.cpp") target_link_libraries(server PRIVATE PkgConfig::deps hyprwire) add_dependencies(tests server) add_executable(fork "${CMAKE_SOURCE_DIR}/tests/Fork.cpp" "tests/generated/test_protocol_v1-client.cpp" "tests/generated/test_protocol_v1-server.cpp") target_link_libraries(fork PRIVATE PkgConfig::deps hyprwire) add_dependencies(tests fork) else() message(STATUS "building tests is disabled") endif() # Installation install(TARGETS hyprwire) install(DIRECTORY "include/hyprwire" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_BINARY_DIR}/hyprwire.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) hyprwm-hyprwire-37bc90e/LICENSE000066400000000000000000000027371514062423500163450ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2025, 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. hyprwm-hyprwire-37bc90e/README.md000066400000000000000000000014751514062423500166150ustar00rootroot00000000000000## hyprwire A fast and consistent wire protocol for IPC ## What is hyprwire Hyprwire is a fast and consistent wire protocol, and its implementation. This is essentially a "method" for processes to talk to each other. ### How does hyprwire differ from other things? Hyprwire is heavily inspired by Wayland, and heavily anti-inspired by D-Bus. Hyprwire is: - Strict: both sides need to be on the same page to communicate. No "random data" is allowed. - Fast: initial handshakes are very simple and allow for quick information exchange (including one-shot operations) - Simple to use: the API uses modern C++ and abstracts away any memory-sensitive operations - Simple internally: the protocol itself is simple and straightforward to parse / write your own implementation ### Developer wire docs See [WIRE.md](./docs/WIRE.md)hyprwm-hyprwire-37bc90e/VERSION000066400000000000000000000000051514062423500163720ustar00rootroot000000000000000.3.0hyprwm-hyprwire-37bc90e/docs/000077500000000000000000000000001514062423500162575ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/docs/WIRE.md000066400000000000000000000122371514062423500173540ustar00rootroot00000000000000# Wire Docs The hyprwire format is split into _messages_. Each message is always in the form of: ``` [code : 1 byte][content : ...][end : 1 byte] ``` Content can be any length, and that needs to be verified by the parser. Each content message contains variables, defined by a magic which defines what follows. ### Message codes The message codes are documented alongside their parameters in [src/core/message/MessageType.hpp](../src/core/message/MessageType.hpp). ### Message content Each code has a hard-defined content, as can be seen in the MessageType header. For messages with a `...` at the end, the message should be read until an `END` is received in place where the next argument would be. ### Wire content types Content is split into parameters, which are essentially variables. These are defined in [include/hyprwire/core/types/MessageMagic.hpp](../include/hyprwire/core/types/MessageMagic.hpp). ### Example wire message An example, using `HW_MESSAGE_TYPE_SUP`, which takes a str: ``` 0x01 0x20 0x03 0x56 0x41 0x58 0x00 | | | | | | | SUP | 3 Bytes | | | | VARCHAR V A X END ``` If a message does not end with an `END`, or a message code is encountered that is not valid, or a content type is invalid, all those must result in a fatal protocol error. As you can see, after the `SUP` code, we just read arguments until an `END`. If instead of the `END`, we'd see e.g. `0x10` which means `UINT`, we would read 4 more bytes and repeat until we reach `END`. In the above example, that would be a protocol error, as `SUP` expects a string of length 3 and nothing more, but other messages might accept `n` arguments. ## Estabilishing a connection Estabilishing a connection is initiated by the client. The client must send `SUP` to the server with a single parameter, a string of `"VAX"`. That's because I am a selfish asshole. The server must respond with `HANDSHAKE_BEGIN`, with an array of `uint`s describing versions of the protocol it supports. Currently, that's just `[1]`. The client must send `HANDSHAKE_ACK` with the chosen version, that is `1`. The server must reply with `HANDSHAKE_PROTOCOLS`. This contains an array of strings with supported protocols. For example, if the server supports `my_protocol` at revision 2, and `my_other_protocol` at revision 1, it should reply with `["my_protocol@2", "my_other_protocol@1"]`. As you can see, the revision is appended with an `@[ver]`. Once this is sent, the handshake is considered complete. ## Once connection is alive Once the connection is alive, we can proceed with regular communication. In order to bind to a protocol exposed, you must send a `BIND_PROTOCOL` with a `sequence` and a protocol spec string, e.g. `my_protocol@2`. > ![NOTE] > A sequence is a u32 that must not repeat. Keep a sequence counter, and every time you need a seq, send > last_seq + 1. Once the bind is successful on the server, the server will respond with `NEW_OBJECT`. This will contain an object handle id and the client-provided sequence. This object handle id can be used to interact with the protocol manager object (which is the first object in the protocol spec). ### Generic messages Generic messages are described by protocol XMLs. These should send `u32` with the object ID from `NEW_OBJECT`, a method ID, and then the arguments as defined in the XML. The method ID is an identifier. These IDs follow the XML ordering, and are per-side, which means `s2c` methods and `c2s` methods have independent counters. For example: ``` s2c: A c2s: B s2c: C ``` method A = ID 0, method B = ID 0, method C = ID 1. ### Fatal errors If a client commits a fatal protocol error, a `FATAL_PROTOCOL_ERROR` message is sent and the connection is terminated. That contains a `u32` with the object handle id that the error was committed on (or 0 if it's not related to an object), a `u32` with the error id (or -1 if it's not an enum'd error) and a `varchar` with the error message. ### Roundtrips If the client wants to synchronize state, e.g. wait for the server to process requests it's sent to it, it can request a roundtrip. `ROUNDTRIP_REQUEST` is sent with a sequence from the client, and the server must respond with `ROUNDTRIP_DONE` once it receives it and processes all pending events that came before receiving it. ### Creating objects Some `GENERIC_PROTOCOL_MESSAGE`s might create objects. In the protocol XMLs, that's done with ``. In those cases, the first argument from the "data" part of the message (everything after object + method) should be a `seq` which will be used to assign an ID to the new object with a `NEW_OBJECT` event from the server. After the seq, regular message arguments should be passed. Object IDs have no requirements. The server may choose them however it wants, as long as two objects that are alive do not share an ID. The hyprwire reference server does a simple increment. ### Destroying objects If a method is a destructor in the XML, calling the method must destroy the object on the server, and any subsequent calls to the ID must raise a protocol error, unless the ID has already been reassigned to a new object. ### Sample XML protocol spec See [protocol-v1.xml](../tests/protocol-v1.xml) hyprwm-hyprwire-37bc90e/flake.lock000066400000000000000000000031011514062423500172560ustar00rootroot00000000000000{ "nodes": { "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1759609323, "narHash": "sha256-5c9sJ4CFdmYJ/EcIUSyzo3CZu3fR+k5r7lnOrtZ8sWA=", "owner": "hyprwm", "repo": "hyprutils", "rev": "9ab64319e95374934aac5406df4e69fee77345ff", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1759381078, "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", "owner": "NixOS", "repo": "nixpkgs", "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprutils": "hyprutils", "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 } hyprwm-hyprwire-37bc90e/flake.nix000066400000000000000000000020531514062423500171310ustar00rootroot00000000000000{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprutils = { url = "github:hyprwm/hyprutils"; inputs = { nixpkgs.follows = "nixpkgs"; systems.follows = "systems"; }; }; }; outputs = inputs@{ self, nixpkgs, systems, ... }: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem ( system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [ hyprwire ]; } ); in { overlays = import ./nix/overlays.nix { inherit self lib inputs; }; packages = eachSystem (system: { default = self.packages.${system}.hyprwire; inherit (pkgsFor.${system}) hyprwire hyprwire-with-tests; }); checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } hyprwm-hyprwire-37bc90e/hyprwire.pc.in000066400000000000000000000003701514062423500201310ustar00rootroot00000000000000prefix=@PREFIX@ includedir=@INCLUDE@ libdir=@LIBDIR@ Name: hyprwire URL: https://github.com/hyprwm/hyprwire Description: A fast and consistent wire protocol for IPC Version: @HYPRWIRE_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lhyprwire hyprwm-hyprwire-37bc90e/include/000077500000000000000000000000001514062423500167525ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/include/hyprwire/000077500000000000000000000000001514062423500206235ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/include/hyprwire/core/000077500000000000000000000000001514062423500215535ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/include/hyprwire/core/ClientSocket.hpp000066400000000000000000000041101514062423500246470ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { class IProtocolClientImplementation; class IProtocolSpec; class IObject; class IClientSocket { public: virtual ~IClientSocket() = default; static Hyprutils::Memory::CSharedPointer open(const std::string& path); // IClientSocket takes ownership of the fd. static Hyprutils::Memory::CSharedPointer open(const int fd); /* Add an implementation to the socket */ virtual void addImplementation(Hyprutils::Memory::CSharedPointer&&) = 0; /* Synchronously dispatch pending events. Returns false on failure. */ virtual bool dispatchEvents(bool block = false) = 0; /* Extract the loop FD. FD is owned by this socket, do not close it. */ virtual int extractLoopFD() = 0; /* Wait for proper connection to be estabilished. */ virtual bool waitForHandshake() = 0; /* Get a protocol spec from the server list. If the spec is supported, will be returned. */ virtual Hyprutils::Memory::CSharedPointer getSpec(const std::string& name) = 0; /* Bind a protocol object */ virtual Hyprutils::Memory::CSharedPointer bindProtocol(const Hyprutils::Memory::CSharedPointer& spec, uint32_t version) = 0; /* Get an object from an id */ virtual Hyprutils::Memory::CSharedPointer objectForId(uint32_t id) = 0; /* Perform a roundtrip */ virtual void roundtrip() = 0; /* Check if handshake has been estabilished */ virtual bool isHandshakeDone() = 0; /* Get an object from a sequence number */ virtual Hyprutils::Memory::CSharedPointer objectForSeq(uint32_t seq) = 0; protected: IClientSocket() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/ServerSocket.hpp000066400000000000000000000036271514062423500247130ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { class IProtocolServerImplementation; class IObject; class IServerClient { public: virtual ~IServerClient(); virtual int getPID() = 0; protected: IServerClient() = default; }; class IServerSocket { public: virtual ~IServerSocket() = default; static Hyprutils::Memory::CSharedPointer open(const std::string& path); // anonymous socket, you can add and remove clients manually static Hyprutils::Memory::CSharedPointer open(); /* Add an implementation to the socket */ virtual void addImplementation(Hyprutils::Memory::CSharedPointer&&) = 0; /* Create an object after client's request */ virtual Hyprutils::Memory::CSharedPointer createObject(Hyprutils::Memory::CSharedPointer client, Hyprutils::Memory::CSharedPointer reference, const std::string& object, uint32_t seq) = 0; /* Synchronously dispatch pending events. Returns false on failure. */ virtual bool dispatchEvents(bool block = false) = 0; /* Extract the loop FD. FD is owned by this socket, do not close it. */ virtual int extractLoopFD() = 0; /* Add a client by fd manually. Hyprwire takes ownership of the fd. */ virtual Hyprutils::Memory::CSharedPointer addClient(int fd) = 0; /* Remove a client by fd. Do not close the fd, hyprwire will manage it. Returns true if it removed anything. */ virtual bool removeClient(int fd) = 0; protected: IServerSocket() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/000077500000000000000000000000001514062423500246005ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/ClientImpl.hpp000066400000000000000000000012441514062423500273520ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprwire { class IProtocolSpec; struct SClientObjectImplementation { std::string objectName = ""; uint32_t version = 0; }; class IProtocolClientImplementation { public: virtual ~IProtocolClientImplementation(); virtual Hyprutils::Memory::CSharedPointer protocol() = 0; virtual std::vector> implementation() = 0; protected: IProtocolClientImplementation() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/Object.hpp000066400000000000000000000025261514062423500265240ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprwire { class IServerClient; class IServerSocket; class IClientSocket; class IObject { public: virtual ~IObject(); virtual uint32_t call(uint32_t id, ...) = 0; virtual void listen(uint32_t id, void* fn) = 0; virtual void setData(void* data); virtual void* getData(); virtual void setOnDestroy(std::function&& fn); virtual Hyprutils::Memory::CSharedPointer self() = 0; // only for server objects virtual Hyprutils::Memory::CSharedPointer serverSock(); virtual Hyprutils::Memory::CSharedPointer client() = 0; virtual void error(uint32_t id, const std::string_view& message); // only for client objects virtual Hyprutils::Memory::CSharedPointer clientSock(); protected: IObject() = default; private: void* m_data = nullptr; std::function m_onDestroy; }; }; hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/ServerImpl.hpp000066400000000000000000000016051514062423500274030ustar00rootroot00000000000000#pragma once #include #include #include #include namespace Hyprwire { class IProtocolSpec; class IObject; struct SServerObjectImplementation { std::string objectName = ""; uint32_t version = 0; std::function)> onBind; }; class IProtocolServerImplementation { public: virtual ~IProtocolServerImplementation(); virtual Hyprutils::Memory::CSharedPointer protocol() = 0; virtual std::vector> implementation() = 0; protected: IProtocolServerImplementation() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/Spec.hpp000066400000000000000000000010551514062423500262040ustar00rootroot00000000000000#pragma once #include #include #include #include "Types.hpp" namespace Hyprwire { class IProtocolSpec { public: virtual ~IProtocolSpec(); virtual std::string specName() = 0; virtual uint32_t specVer() = 0; /* First object is the manager which will be created upon bind. */ virtual std::vector> objects() = 0; protected: IProtocolSpec() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/implementation/Types.hpp000066400000000000000000000012141514062423500264130ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprwire { struct SMethod { uint32_t idx = 0; std::vector params; std::string returnsType = ""; uint32_t since = 0; }; class IProtocolObjectSpec { public: virtual ~IProtocolObjectSpec() = default; virtual std::string objectName() = 0; virtual const std::vector& c2s() = 0; virtual const std::vector& s2c() = 0; protected: IProtocolObjectSpec() = default; }; };hyprwm-hyprwire-37bc90e/include/hyprwire/core/types/000077500000000000000000000000001514062423500227175ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/include/hyprwire/core/types/MessageMagic.hpp000066400000000000000000000031031514062423500257520ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { enum eMessageMagic : uint8_t { /* Signifies an end of a message */ HW_MESSAGE_MAGIC_END = 0x0, /* Primitive type identifiers. These are all 4 bytes. SEQ and OBJECT_ID are U32. */ HW_MESSAGE_MAGIC_TYPE_UINT = 0x10, HW_MESSAGE_MAGIC_TYPE_INT = 0x11, HW_MESSAGE_MAGIC_TYPE_F32 = 0x12, HW_MESSAGE_MAGIC_TYPE_SEQ = 0x13, HW_MESSAGE_MAGIC_TYPE_OBJECT_ID = 0x14, /* Variable length types. VLQ = Variable Length Quantity. See https://en.wikipedia.org/wiki/Variable-length_quantity. */ /* [magic : 1B][len : VLQ][data : len B] */ HW_MESSAGE_MAGIC_TYPE_VARCHAR = 0x20, /* [magic : 1B][type : 1B][n_els : VLQ]{ [data...] } Note that ARRAY strips the magic from each element, as it's already in the arr. For example, for UINT data would be packs of 4 bytes. For a string, it's [len : VLQ][data : len B]. */ HW_MESSAGE_MAGIC_TYPE_ARRAY = 0x21, /* [magic : 1B][id : UINT][name_len : VLQ][object name ...] */ HW_MESSAGE_MAGIC_TYPE_OBJECT = 0x22, /* Special types */ /* [magic : 1B] FD has nothing but the magic. The FD is passed via control messages (sendmsg) and should be read with recvmsg. */ HW_MESSAGE_MAGIC_TYPE_FD = 0x40, }; };hyprwm-hyprwire-37bc90e/include/hyprwire/hyprwire.hpp000066400000000000000000000004111514062423500232010ustar00rootroot00000000000000#pragma once #include "./core/ClientSocket.hpp" #include "./core/ServerSocket.hpp" #include "./core/implementation/Spec.hpp" #include "./core/implementation/ServerImpl.hpp" #include "./core/implementation/ClientImpl.hpp" #include "./core/implementation/Object.hpp"hyprwm-hyprwire-37bc90e/nix/000077500000000000000000000000001514062423500161255ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/nix/default.nix000066400000000000000000000010161514062423500202670ustar00rootroot00000000000000{ lib, stdenv, cmake, pkg-config, hyprutils, libffi, pugixml, version ? "git", doCheck ? false, }: stdenv.mkDerivation { pname = "hyprwire"; inherit version doCheck; src = ../.; nativeBuildInputs = [ cmake pkg-config ]; buildInputs = [ hyprutils libffi pugixml ]; meta = { homepage = "https://github.com/hyprwm/hyprwire"; description = "A fast and consistent wire protocol for IPC"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; }; } hyprwm-hyprwire-37bc90e/nix/overlays.nix000066400000000000000000000014171514062423500205140ustar00rootroot00000000000000{ lib, inputs, self, }: let mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); in { default = inputs.self.overlays.hyprwire; hyprwire = lib.composeManyExtensions [ inputs.hyprutils.overlays.default (final: prev: { hyprwire = prev.callPackage ./default.nix { stdenv = prev.gcc15Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); }; hyprwire-with-tests = final.hyprwire.override {doCheck = true;}; }) ]; } hyprwm-hyprwire-37bc90e/scanner/000077500000000000000000000000001514062423500167605ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/scanner/CMakeLists.txt000066400000000000000000000035671514062423500215330ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) project( hyprwire-scanner DESCRIPTION "A protocol code generator for hyprwire" VERSION ${HYPRWIRE_VERSION}) include(GNUInstallDirs) include(CMakePackageConfigHelpers) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") # 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="${HYPRWIRE_VERSION}") configure_file(hyprwire-scanner.pc.in hyprwire-scanner.pc @ONLY) # dependencies message(STATUS "Checking deps...") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET pugixml) find_library(librt rt) if("${librt}" MATCHES "librt-NOTFOUND") unset(LIBRT) else() set(LIBRT rt) endif() add_executable(hyprwire-scanner main.cpp) target_link_libraries(hyprwire-scanner PRIVATE ${LIBRT} Threads::Threads PkgConfig::deps) target_include_directories(hyprwire-scanner PUBLIC "${CMAKE_SOURCE_DIR}/include") configure_package_config_file( hyprwire-scanner-config.cmake.in hyprwire-scanner-config.cmake INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/hyprwire-scanner" PATH_VARS CMAKE_INSTALL_BINDIR) write_basic_package_version_file( "hyprwire-scanner-config-version.cmake" VERSION "${VERSION}" ARCH_INDEPENDENT COMPATIBILITY AnyNewerVersion) # Installation install(TARGETS hyprwire-scanner) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hyprwire-scanner.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hyprwire-scanner-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/hyprwire-scanner-config-version.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hyprwire-scanner) hyprwm-hyprwire-37bc90e/scanner/hyprwire-scanner-config.cmake.in000066400000000000000000000010671514062423500251360ustar00rootroot00000000000000@PACKAGE_INIT@ set_and_check(BINDIR "@PACKAGE_CMAKE_INSTALL_BINDIR@") function(hyprwire_protocol targets protoName protoPath outputPath) add_custom_command( OUTPUT "${outputPath}/${protoName}.cpp" COMMAND "${BINDIR}/hyprwire-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(hyprwire-scanner) hyprwm-hyprwire-37bc90e/scanner/hyprwire-scanner.pc.in000066400000000000000000000002661514062423500232150ustar00rootroot00000000000000hyprwire_scanner=@PREFIX@/bin/hyprwire-scanner Name: Hyprwire Scanner URL: https://github.com/hyprwm/hyprwire Description: A protocol code generator for hyprwire Version: @VERSION@ hyprwm-hyprwire-37bc90e/scanner/main.cpp000066400000000000000000000722001514062423500204110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include struct SRequestArgument { Hyprwire::eMessageMagic magic = Hyprwire::HW_MESSAGE_MAGIC_END, arrType = Hyprwire::HW_MESSAGE_MAGIC_END; std::string interface; std::string name; bool allowNull = false; bool isEnum = false; }; struct SMethodSpec { std::vector args; std::string name; uint32_t since; std::string returns = ""; bool destructor = false; uint32_t idx = 0; }; struct SObjectSpec { std::vector s2c; std::vector c2s; std::string name, nameCamel; int version = 1; }; struct SEnumSpec { std::string interface, nameCamel; std::vector> entries; }; static std::vector OBJECT_SPECS; static std::vector ENUM_SPECS; static bool clientCode = false; static std::string HEADER_PROTOCOL, HEADER_IMPL; static std::string SOURCE; static struct { std::string name; std::string nameOriginal; std::string fileName; uint32_t version = 1; } PROTO_DATA; // static 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; } static std::string capitalize(std::string str) { if (str.empty()) return ""; str[0] = ::toupper(str[0]); return str; } static std::string uppercase(std::string str) { if (str.empty()) return ""; std::ranges::transform(str, str.begin(), ::toupper); return str; } static Hyprwire::eMessageMagic strToMagic(const std::string_view& sv) { if (sv == "varchar") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_VARCHAR; if (sv == "uint") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT; if (sv == "enum") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT; if (sv == "int") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_INT; if (sv == "f32") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_F32; if (sv == "fd") return Hyprwire::HW_MESSAGE_MAGIC_TYPE_FD; // if (sv == "object") // return Hyprwire::HW_MESSAGE_MAGIC_TYPE_OBJECT_ID; if (sv.starts_with("array ")) return Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY; return Hyprwire::HW_MESSAGE_MAGIC_END; } static std::string magicToString(Hyprwire::eMessageMagic m, Hyprwire::eMessageMagic arrType = Hyprwire::HW_MESSAGE_MAGIC_END) { switch (m) { case Hyprwire::HW_MESSAGE_MAGIC_TYPE_VARCHAR: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_VARCHAR"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_INT: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_INT"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_F32: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_F32"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_FD: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_FD"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY: return "Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY, " + magicToString(arrType); default: return ""; } } static std::string argToC(Hyprwire::eMessageMagic m) { switch (m) { case Hyprwire::HW_MESSAGE_MAGIC_TYPE_VARCHAR: return "const char*"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT: return "uint32_t"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_INT: return "int32_t"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_F32: return "float"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_FD: return "int"; default: return ""; } } static std::string argToC(const SRequestArgument& arg) { switch (arg.magic) { case Hyprwire::HW_MESSAGE_MAGIC_TYPE_VARCHAR: return "const char*"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_UINT: { if (!arg.isEnum) return "uint32_t"; return camelize(PROTO_DATA.nameOriginal + "_" + arg.interface); } case Hyprwire::HW_MESSAGE_MAGIC_TYPE_INT: return "int32_t"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_F32: return "float"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_FD: return "int"; case Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY: return "const std::vector<" + argToC(arg.arrType) + ">&"; default: return ""; } } static std::string argsToC(const std::vector& args, bool noNames = false, bool noTypes = false, bool addSequence = false, bool pureC = false, bool unC = false) { std::string cstr; if (addSequence) { if (noTypes) cstr += "seq, "; else { if (noNames) cstr += "uint32_t, "; else cstr += "uint32_t seq, "; } } for (const auto& m : args) { if (m.arrType != Hyprwire::HW_MESSAGE_MAGIC_END && pureC) { if (noTypes) cstr += std::format("{}.data(), (uint32_t){}.size(), ", m.name, m.name); else { if (noNames) cstr += std::format("{}*, uint32_t, ", argToC(m.arrType)); else cstr += std::format("{}* {}, uint32_t {}, ", argToC(m.arrType), m.name, m.name + "_len"); } } else if (m.arrType != Hyprwire::HW_MESSAGE_MAGIC_END && unC) { if (noTypes) cstr += std::format("std::vector<{}>{{ {}, {} + {} }}, ", argToC(m.arrType), m.name, m.name, m.name + "_len"); else { if (noNames) cstr += std::format("{}, ", argToC(m)); else cstr += std::format("{} {}, ", argToC(m), m.name); } } else { if (noTypes) cstr += std::format("{}, ", m.name); else { if (noNames) cstr += std::format("{}, ", argToC(m)); else cstr += std::format("{} {}, ", argToC(m), m.name); } } } if (!cstr.empty()) cstr = cstr.substr(0, cstr.size() - 2); return cstr; } static bool scanProtocol(const pugi::xml_document& doc) { for (const auto& c : doc.child("protocol").children()) { if (c.name() != std::string_view{"enum"}) continue; const auto& object = c; SEnumSpec spec; spec.interface = PROTO_DATA.nameOriginal + "_" + object.attribute("name").as_string(); spec.nameCamel = camelize(spec.interface); for (const auto& c : object.children()) { spec.entries.emplace_back(std::make_pair<>(c.attribute("idx").as_int(), c.attribute("name").as_string())); } ENUM_SPECS.emplace_back(std::move(spec)); } for (const auto& c : doc.child("protocol").children()) { if (c.name() != std::string_view{"object"}) continue; const auto& object = c; SObjectSpec spec; spec.name = object.attribute("name").as_string(); spec.nameCamel = camelize(spec.name); spec.version = object.attribute("version").as_int(); // Generate s2c methods uint32_t currentIdx = 0; for (const auto& m : c.children()) { if (m.name() != std::string_view{"s2c"}) continue; SMethodSpec method; method.name = m.attribute("name").as_string(); method.destructor = m.attribute("destructor").as_bool(); method.since = m.attribute("since").as_int(); method.idx = currentIdx++; for (const auto& param : m.children()) { if (param.name() == std::string_view{"arg"}) { auto& a = method.args.emplace_back(SRequestArgument{ .magic = strToMagic(param.attribute("type").as_string()), .name = param.attribute("name").as_string(), .allowNull = param.attribute("allow_null").as_bool(), }); if (a.magic == Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY) a.arrType = strToMagic(std::string{param.attribute("type").as_string()}.substr(6)); } if (param.name() == std::string_view{"returns"}) { method.returns = param.attribute("iface").as_string(); continue; } } spec.s2c.emplace_back(std::move(method)); } // Generate c2s methods currentIdx = 0; for (const auto& m : c.children()) { if (m.name() != std::string_view{"c2s"}) continue; SMethodSpec method; method.name = m.attribute("name").as_string(); method.destructor = m.attribute("destructor").as_bool(); method.since = m.attribute("since").as_int(); method.idx = currentIdx++; for (const auto& param : m.children()) { if (param.name() == std::string_view{"arg"}) { auto& a = method.args.emplace_back(SRequestArgument{ .magic = strToMagic(param.attribute("type").as_string()), .interface = param.attribute("interface").as_string(""), .name = param.attribute("name").as_string(), .allowNull = param.attribute("allow_null").as_bool(), .isEnum = param.attribute("type").as_string() == std::string_view{"enum"}, }); if (a.magic == Hyprwire::HW_MESSAGE_MAGIC_TYPE_ARRAY) a.arrType = strToMagic(std::string{param.attribute("type").as_string()}.substr(6)); continue; } if (param.name() == std::string_view{"returns"}) { method.returns = param.attribute("iface").as_string(); continue; } } spec.c2s.emplace_back(std::move(method)); } OBJECT_SPECS.emplace_back(std::move(spec)); } return true; } static bool generateProtocolHeader(const pugi::xml_document& doc) { HEADER_PROTOCOL += R"#( #pragma once #include #include #include #include )#"; // begin enums for (const auto& ENUM : ENUM_SPECS) { HEADER_PROTOCOL += std::format(R"#( enum {} : uint32_t {{ )#", ENUM.nameCamel); for (const auto& [id, name] : ENUM.entries) { HEADER_PROTOCOL += std::format("\t{} = {},\n", uppercase(ENUM.interface + "_" + name), id); } HEADER_PROTOCOL += "};\n"; } // begin objects for (const auto& object : OBJECT_SPECS) { HEADER_PROTOCOL += std::format(R"#( class C{}Spec : public Hyprwire::IProtocolObjectSpec {{ public: C{}Spec() = default; virtual ~C{}Spec() = default; virtual std::string objectName() {{ return "{}"; }} )#", capitalize(object.nameCamel), capitalize(object.nameCamel), capitalize(object.nameCamel), object.name); // Add C2S method arr and fn HEADER_PROTOCOL += "\n\tstd::vector m_c2s = {"; for (const auto& m : object.c2s) { std::string argArrayStr; for (const auto& p : m.args) { argArrayStr += magicToString(p.magic, p.arrType) + ", "; } if (!argArrayStr.empty()) argArrayStr = argArrayStr.substr(0, argArrayStr.size() - 2); HEADER_PROTOCOL += std::format(R"#( Hyprwire::SMethod{{ .idx = {}, .params = {{ {} }}, .returnsType = "{}", .since = {}, }},)#", m.idx, argArrayStr, m.returns, m.since); } if (!object.c2s.empty()) HEADER_PROTOCOL.pop_back(); HEADER_PROTOCOL += R"#( }; virtual const std::vector& c2s() { return m_c2s; } )#"; HEADER_PROTOCOL += "\n\tstd::vector m_s2c = {"; for (const auto& m : object.s2c) { std::string argArrayStr; for (const auto& p : m.args) { argArrayStr += magicToString(p.magic, p.arrType) + ", "; } if (!argArrayStr.empty()) argArrayStr = argArrayStr.substr(0, argArrayStr.size() - 2); HEADER_PROTOCOL += std::format(R"#( Hyprwire::SMethod{{ .idx = {}, .params = {{ {} }}, .since = {}, }},)#", m.idx, argArrayStr, m.since); } if (!object.s2c.empty()) HEADER_PROTOCOL.pop_back(); HEADER_PROTOCOL += R"#( }; virtual const std::vector& s2c() { return m_s2c; } }; )#"; } // end objects // protocol object std::string objectVecStr = ""; for (const auto& o : OBJECT_SPECS) { objectVecStr += std::format("Hyprutils::Memory::makeShared(), ", capitalize(o.nameCamel)); } objectVecStr = objectVecStr.substr(0, objectVecStr.size() - 2); HEADER_PROTOCOL += std::format(R"#( class C{}ProtocolSpec : public Hyprwire::IProtocolSpec {{ public: C{}ProtocolSpec() = default; virtual ~C{}ProtocolSpec() = default; virtual std::string specName() {{ return "{}"; }} virtual uint32_t specVer() {{ return {}; }} virtual std::vector> objects() {{ return {{ {} }}; }} }}; )#", capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), PROTO_DATA.nameOriginal, PROTO_DATA.version, objectVecStr); return true; } static bool generateClientCodeHeader(const pugi::xml_document& doc) { HEADER_IMPL += std::format(R"#( #pragma once #include #include "{}-spec.hpp" )#", PROTO_DATA.nameOriginal); for (const auto& o : OBJECT_SPECS) { HEADER_IMPL += std::format(R"#( class CC{}Object {{ public: CC{}Object(Hyprutils::Memory::CSharedPointer&& object); ~CC{}Object(); Hyprutils::Memory::CSharedPointer getObject() {{ return m_object.lock(); }} )#", capitalize(o.nameCamel), capitalize(o.nameCamel), capitalize(o.nameCamel)); for (const auto& m : o.c2s) { const auto RETURN_TYPE = m.returns.empty() ? "void" : "Hyprutils::Memory::CSharedPointer"; HEADER_IMPL += std::format(R"#( {} send{}({}); )#", RETURN_TYPE, capitalize(camelize(m.name)), argsToC(m.args)); } for (const auto& m : o.s2c) { HEADER_IMPL += std::format(R"#( void set{}(std::function&& fn); )#", capitalize(camelize(m.name)), argsToC(m.args, true)); } HEADER_IMPL += "\n private:\n\tstruct {\n"; for (const auto& m : o.s2c) { HEADER_IMPL += std::format(R"#( std::function {}; )#", argsToC(m.args, true), m.name); } HEADER_IMPL += R"#( } m_listeners; Hyprutils::Memory::CWeakPointer m_object; }; )#"; } HEADER_IMPL += std::format(R"#( class CC{}Impl : public Hyprwire::IProtocolClientImplementation {{ public: CC{}Impl(uint32_t version); virtual ~CC{}Impl() = default; virtual Hyprutils::Memory::CSharedPointer protocol(); virtual std::vector> implementation(); private: uint32_t m_version = 0; }}; )#", capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name)); return true; } static bool generateClientCodeSource(const pugi::xml_document& doc) { SOURCE += std::format(R"#( #define private public #include "{}-client.hpp" #undef private using namespace Hyprutils::Memory; #define SP CSharedPointer )#", PROTO_DATA.nameOriginal); for (const auto& o : OBJECT_SPECS) { for (const auto& m : o.s2c) { SOURCE += std::format(R"#( static void {}_method{}(Hyprwire::IObject* r{}) {{ auto& fn = rc<{}*>(r->getData())->m_listeners.{}; if (fn) fn({}); }} )#", o.nameCamel, m.idx, m.args.empty() ? "" : ", " + argsToC(m.args, false, false, false, true), std::format("CC{}Object", capitalize(o.nameCamel)), m.name, argsToC(m.args, false, true, false, false, true)); } SOURCE += std::format(R"#( CC{}Object::CC{}Object(Hyprutils::Memory::CSharedPointer&& object) : m_object(std::move(object)) {{ m_object->setData(this); )#", capitalize(o.nameCamel), capitalize(o.nameCamel)); for (const auto& m : o.s2c) { SOURCE += std::format(R"#( m_object->listen({}, rc(::{}_method{}));)#", m.idx, o.nameCamel, m.idx); } SOURCE += std::format(R"#( }} CC{}Object::~CC{}Object() {{ ; // TODO: call destructor if present }})#", capitalize(o.nameCamel), capitalize(o.nameCamel)); for (const auto& m : o.c2s) { if (m.returns.empty()) { SOURCE += std::format(R"#( void CC{}Object::send{}({}) {{ m_object->call({}{}); }} )#", capitalize(o.nameCamel), capitalize(camelize(m.name)), argsToC(m.args), m.idx, m.args.empty() ? "" : ", " + argsToC(m.args, false, true, false, true)); } else { SOURCE += std::format(R"#( SP CC{}Object::send{}({}) {{ auto _seq = m_object->call({}{}); return m_object->clientSock()->objectForSeq(_seq); }} )#", capitalize(o.nameCamel), capitalize(camelize(m.name)), argsToC(m.args), m.idx, m.args.empty() ? "" : ", " + argsToC(m.args, false, true, false, true)); } } for (const auto& m : o.s2c) { SOURCE += std::format(R"#( void CC{}Object::set{}(std::function&& fn) {{ m_listeners.{} = std::move(fn); }} )#", capitalize(o.nameCamel), capitalize(camelize(m.name)), argsToC(m.args, true), m.name); } } SOURCE += std::format(R"#( CC{}Impl::CC{}Impl(uint32_t ver) : m_version(ver) {{ ; }} static auto {}Spec = makeShared(); SP CC{}Impl::protocol() {{ return {}Spec; }} std::vector> CC{}Impl::implementation() {{ return {{ )#", capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), PROTO_DATA.name, capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), PROTO_DATA.name, capitalize(PROTO_DATA.name)); for (const auto& o : OBJECT_SPECS) { SOURCE += std::format(R"#( makeShared(Hyprwire::SClientObjectImplementation{{ .objectName = "{}", .version = m_version, }}), )#", o.name); } SOURCE += "};\n}\n"; return true; } static bool generateServerCodeHeader(const pugi::xml_document& doc) { HEADER_IMPL += std::format(R"#( #pragma once #include #include "{}-spec.hpp" )#", PROTO_DATA.nameOriginal); for (const auto& o : OBJECT_SPECS) { HEADER_IMPL += std::format(R"#( class C{}Object {{ public: C{}Object(Hyprutils::Memory::CSharedPointer&& object); ~C{}Object(); Hyprutils::Memory::CSharedPointer getObject() {{ return m_object.lock(); }} void setOnDestroy(std::function&& fn) {{ m_object->setOnDestroy(std::move(fn)); }} void error(uint32_t code, const std::string_view& sv) {{ m_object->error(code, sv); }} static const char* name() {{ return "{}"; }} )#", capitalize(o.nameCamel), capitalize(o.nameCamel), capitalize(o.nameCamel), o.name); for (const auto& m : o.s2c) { HEADER_IMPL += std::format(R"#( void send{}({}); )#", capitalize(camelize(m.name)), argsToC(m.args)); } for (const auto& m : o.c2s) { HEADER_IMPL += std::format(R"#( void set{}(std::function&& fn); )#", capitalize(camelize(m.name)), argsToC(m.args, true, false, !m.returns.empty())); } HEADER_IMPL += "\n private:\n\tstruct {\n"; for (const auto& m : o.c2s) { HEADER_IMPL += std::format(R"#( std::function {}; )#", argsToC(m.args, true, false, !m.returns.empty()), m.name); } HEADER_IMPL += R"#( } m_listeners; Hyprutils::Memory::CWeakPointer m_object; }; )#"; } HEADER_IMPL += std::format(R"#( class C{}Impl : public Hyprwire::IProtocolServerImplementation {{ public: C{}Impl(uint32_t version, std::function)>&& bindFn); virtual ~C{}Impl() = default; virtual Hyprutils::Memory::CSharedPointer protocol(); virtual std::vector> implementation(); private: uint32_t m_version = 0; std::function)> m_bindFn; }}; )#", capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name)); return true; } static bool generateServerCodeSource(const pugi::xml_document& doc) { SOURCE += std::format(R"#( #define private public #include "{}-server.hpp" #undef private using namespace Hyprutils::Memory; #define SP CSharedPointer )#", PROTO_DATA.nameOriginal); for (const auto& o : OBJECT_SPECS) { for (const auto& m : o.c2s) { SOURCE += std::format(R"#( static void {}_method{}(Hyprwire::IObject* r{}) {{ auto& fn = rc<{}*>(r->getData())->m_listeners.{}; if (fn) fn({}); }} )#", o.nameCamel, m.idx, m.args.empty() && m.returns.empty() ? "" : ", " + argsToC(m.args, false, false, !m.returns.empty(), true), std::format("C{}Object", capitalize(o.nameCamel)), m.name, argsToC(m.args, false, true, !m.returns.empty(), false, true)); } SOURCE += std::format(R"#( C{}Object::C{}Object(Hyprutils::Memory::CSharedPointer&& object) : m_object(std::move(object)) {{ m_object->setData(this); )#", capitalize(o.nameCamel), capitalize(o.nameCamel)); for (const auto& m : o.c2s) { SOURCE += std::format(R"#( m_object->listen({}, rc(::{}_method{}));)#", m.idx, o.nameCamel, m.idx); } SOURCE += std::format(R"#( }} C{}Object::~C{}Object() {{ ; // TODO: call destructor if present }})#", capitalize(o.nameCamel), capitalize(o.nameCamel)); for (const auto& m : o.s2c) { SOURCE += std::format(R"#( void C{}Object::send{}({}) {{ m_object->call({}{}); }} )#", capitalize(o.nameCamel), capitalize(camelize(m.name)), argsToC(m.args), m.idx, m.args.empty() ? "" : ", " + argsToC(m.args, false, true, false, true)); } for (const auto& m : o.c2s) { SOURCE += std::format(R"#( void C{}Object::set{}(std::function&& fn) {{ m_listeners.{} = std::move(fn); }} )#", capitalize(o.nameCamel), capitalize(camelize(m.name)), argsToC(m.args, true, false, !m.returns.empty()), m.name); } } SOURCE += std::format(R"#( C{}Impl::C{}Impl(uint32_t ver, std::function)>&& bindFn) : m_version(ver), m_bindFn(bindFn) {{ ; }} static auto {}Spec = makeShared(); SP C{}Impl::protocol() {{ return {}Spec; }} std::vector> C{}Impl::implementation() {{ return {{ )#", capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), PROTO_DATA.name, capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name), PROTO_DATA.name, capitalize(PROTO_DATA.name), capitalize(PROTO_DATA.name)); bool first = true; for (const auto& o : OBJECT_SPECS) { if (first) { SOURCE += std::format(R"#( makeShared(Hyprwire::SServerObjectImplementation{{ .objectName = "{}", .version = m_version, .onBind = [this] (Hyprutils::Memory::CSharedPointer r) {{ if (m_bindFn) m_bindFn(r); }} }}), )#", o.name); } else { SOURCE += std::format(R"#( makeShared(Hyprwire::SServerObjectImplementation{{ .objectName = "{}", .version = m_version, }}), )#", o.name); } first = false; } SOURCE += "};\n}\n"; return true; } 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 (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 (auto x = doc.load_file(protopath.c_str()); !x) { std::cerr << "Couldn't load proto: " << x.description() << std::endl; 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); PROTO_DATA.version = doc.child("protocol").attribute("version").as_int(1); const auto COPYRIGHT = std::format("// Generated with hyprwire-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()}); scanProtocol(doc); generateProtocolHeader(doc); if (clientCode) { generateClientCodeHeader(doc); generateClientCodeSource(doc); } else { generateServerCodeHeader(doc); generateServerCodeSource(doc); } std::ofstream ofs(outpath + "/" + PROTO_DATA.nameOriginal + "-spec.hpp", std::ios::trunc); ofs << COPYRIGHT << HEADER_PROTOCOL; ofs.close(); if (clientCode) { ofs = std::ofstream(outpath + "/" + PROTO_DATA.nameOriginal + "-client.hpp", std::ios::trunc); ofs << COPYRIGHT << HEADER_IMPL; ofs.close(); ofs = std::ofstream(outpath + "/" + PROTO_DATA.nameOriginal + "-client.cpp", std::ios::trunc); ofs << COPYRIGHT << SOURCE; ofs.close(); } else { ofs = std::ofstream(outpath + "/" + PROTO_DATA.nameOriginal + "-server.hpp", std::ios::trunc); ofs << COPYRIGHT << HEADER_IMPL; ofs.close(); ofs = std::ofstream(outpath + "/" + PROTO_DATA.nameOriginal + "-server.cpp", std::ios::trunc); ofs << COPYRIGHT << SOURCE; ofs.close(); } return 0; } hyprwm-hyprwire-37bc90e/src/000077500000000000000000000000001514062423500161165ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/Macros.hpp000066400000000000000000000055621514062423500200630ustar00rootroot00000000000000#pragma once #include #include #include #include #include "./helpers/Env.hpp" static auto STEADY_MILLIS_START = std::chrono::steady_clock::now(); inline float steadyMillis() { return (float)std::chrono::duration_cast(std::chrono::steady_clock::now() - STEADY_MILLIS_START).count() / 1000.F; } #define TRACE(expr) \ { \ if (Hyprwire::Env::isTrace()) { \ expr; \ } \ } #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ std::cout << std::format("\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ std::cout << "[HT] Assertion failed!"; \ std::fflush(stdout); \ raise(SIGABRT); \ } #define ASSERT(expr) RASSERT(expr, "?") #ifndef HYPRTWIRE_DEBUG #define UNREACHABLE() std::unreachable(); #else #define UNREACHABLE() RASSERT(false, "Reached an unreachable block"); #endifhyprwm-hyprwire-37bc90e/src/core/000077500000000000000000000000001514062423500170465ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/client/000077500000000000000000000000001514062423500203245ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/client/ClientObject.cpp000066400000000000000000000023121514062423500233730ustar00rootroot00000000000000#include "ClientObject.hpp" #include "ClientSocket.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/FFI.hpp" #include "../../Macros.hpp" #include "../message/MessageType.hpp" #include "../message/MessageParser.hpp" #include "../message/messages/GenericProtocolMessage.hpp" #include #include #include using namespace Hyprwire; CClientObject::CClientObject(SP client) : m_client(client) { ; } CClientObject::~CClientObject() { TRACE(Debug::log(TRACE, "destroying object {}", m_id)); } const std::vector& CClientObject::methodsOut() { return m_spec->c2s(); } const std::vector& CClientObject::methodsIn() { return m_spec->s2c(); } void CClientObject::errd() { if (m_client) m_client->m_error = true; } void CClientObject::sendMessage(const IMessage& msg) { if (m_client) m_client->sendMessage(msg); } SP CClientObject::client() { return nullptr; } bool CClientObject::server() { return false; } SP CClientObject::self() { return m_self.lock(); } SP CClientObject::clientSock() { if (!m_client) return nullptr; return m_client.lock(); } hyprwm-hyprwire-37bc90e/src/core/client/ClientObject.hpp000066400000000000000000000021471514062423500234060ustar00rootroot00000000000000#pragma once #include #include #include "ClientSocket.hpp" #include "../../helpers/Memory.hpp" #include "../wireObject/IWireObject.hpp" namespace Hyprwire { class CClientSocket; class CClientObject : public IWireObject { public: CClientObject(SP client); virtual ~CClientObject(); virtual const std::vector& methodsOut(); virtual const std::vector& methodsIn(); virtual void errd(); virtual void sendMessage(const IMessage&); virtual Hyprutils::Memory::CSharedPointer client(); virtual Hyprutils::Memory::CSharedPointer self(); virtual Hyprutils::Memory::CSharedPointer clientSock(); virtual bool server(); WP m_client; }; }; hyprwm-hyprwire-37bc90e/src/core/client/ClientSocket.cpp000066400000000000000000000236161514062423500234270ustar00rootroot00000000000000#include "ClientSocket.hpp" #include "../../helpers/Memory.hpp" #include "../../helpers/Log.hpp" #include "../../Macros.hpp" #include "../message/MessageParser.hpp" #include "../message/messages/IMessage.hpp" #include "../message/messages/Hello.hpp" #include "../message/messages/BindProtocol.hpp" #include "../message/messages/GenericProtocolMessage.hpp" #include "../message/messages/RoundtripRequest.hpp" #include "../socket/SocketHelpers.hpp" #include "../wireObject/IWireObject.hpp" #include "ClientObject.hpp" #include "ServerSpec.hpp" #include #include #include #include #include #include #include #include #include using namespace Hyprwire; using namespace Hyprutils::OS; using namespace Hyprutils::Utils; SP IClientSocket::open(const std::string& path) { SP sock = makeShared(); sock->m_self = sock; if (!sock->attempt(path)) return nullptr; return sock; } SP IClientSocket::open(const int fd) { SP sock = makeShared(); sock->m_self = sock; if (!sock->attemptFromFd(fd)) return nullptr; return sock; } bool CClientSocket::attempt(const std::string& path) { m_fd = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM, 0)}; sockaddr_un serverAddress = {.sun_family = AF_UNIX}; std::error_code ec; if (!std::filesystem::exists(path, ec) || ec) return false; if (path.size() >= 108) return false; strcpy(serverAddress.sun_path, path.c_str()); if (connect(m_fd.get(), (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) { Debug::log(ERR, "err: {}", errno); return false; } m_fd.setFlags(O_NONBLOCK | O_CLOEXEC); m_pollfds = {pollfd{ .fd = m_fd.get(), .events = POLLIN, }}; // send hello instantly sendMessage(CHelloMessage()); return true; } bool CClientSocket::attemptFromFd(const int fd) { m_fd = CFileDescriptor{fd}; m_fd.setFlags(O_NONBLOCK | O_CLOEXEC); m_pollfds = {pollfd{ .fd = m_fd.get(), .events = POLLIN, }}; // send hello instantly sendMessage(CHelloMessage()); return true; } void CClientSocket::addImplementation(SP&& x) { m_impls.emplace_back(std::move(x)); } constexpr const size_t HANDSHAKE_MAX_MS = 5000; // bool CClientSocket::dispatchEvents(bool block) { if (m_error) return false; if (!m_handshakeDone) { const auto MAX_MS = std::chrono::duration_cast(std::chrono::milliseconds(HANDSHAKE_MAX_MS) - (std::chrono::steady_clock::now() - m_handshakeBegin)).count(); int ret = poll(m_pollfds.data(), m_pollfds.size(), block ? MAX_MS : 0); if (block && !ret) { Debug::log(ERR, "handshake error: timed out"); disconnectOnError(); return false; } } if (m_handshakeDone) poll(m_pollfds.data(), m_pollfds.size(), block ? -1 : 0); if (m_pollfds[0].revents & POLLHUP) return false; if (!(m_pollfds[0].revents & POLLIN)) return true; // dispatch auto data = parseFromFd(m_fd); if (data.bad) { Debug::log(ERR, "fatal: received malformed message from server"); disconnectOnError(); return false; } if (data.data.empty()) return false; const auto RET = g_messageParser->handleMessage(data, m_self.lock()); if (RET != MESSAGE_PARSED_OK) { Debug::log(ERR, "fatal: failed to handle message on wire"); disconnectOnError(); return false; } std::erase_if(m_pendingOutgoing, [this](auto& msg) { auto obj = objectForSeq(msg.m_dependsOnSeq); if (!obj) return true; auto wObj = reinterpretPointerCast(obj); if (!wObj->m_id) return false; msg.resolveSeq(wObj->m_id); TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -> Handle deferred: {}", m_fd.get(), steadyMillis(), msg.parseData())); sendMessage(msg); return true; }); return !m_error; } void CClientSocket::sendMessage(const IMessage& message) { TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -> {}", m_fd.get(), steadyMillis(), message.parseData())); // NOLINTNEXTLINE msghdr msg = {0}; // NOLINTNEXTLINE iovec io = {0}; const auto& FDS = message.fds(); // fucking evil! io.iov_base = cc(rc(message.m_data.data())); io.iov_len = message.m_data.size(); msg.msg_iov = &io; msg.msg_iovlen = 1; std::vector controlBuf; if (!FDS.empty()) { controlBuf.resize(CMSG_SPACE(sizeof(int) * FDS.size())); msg.msg_control = controlBuf.data(); msg.msg_controllen = controlBuf.size(); cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int) * FDS.size()); int* data = rc(CMSG_DATA(cmsg)); for (size_t i = 0; i < FDS.size(); ++i) { data[i] = FDS.at(i); } } while (m_fd.isValid()) { int ret = sendmsg(m_fd.get(), &msg, 0); if (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { pollfd pfd = { .fd = m_fd.get(), .events = POLLOUT | POLLWRBAND, }; poll(&pfd, 1, -1); } else break; } } int CClientSocket::extractLoopFD() { return m_fd.get(); } void CClientSocket::serverSpecs(const std::vector& s) { try { for (const auto& specName : s) { size_t atPos = specName.find_last_of('@'); m_serverSpecs.emplace_back(makeShared(specName.substr(0, atPos), std::stoul(specName.substr(atPos + 1)))); } } catch (...) { Debug::log(ERR, "fatal: failed to parse server specs"); disconnectOnError(); } m_handshakeDone = true; } bool CClientSocket::waitForHandshake() { m_handshakeBegin = std::chrono::steady_clock::now(); while (!m_error && !m_handshakeDone) { if (!dispatchEvents(true)) return false; } return !m_error; } bool CClientSocket::isHandshakeDone() { return m_handshakeDone; } SP CClientSocket::getSpec(const std::string& name) { for (const auto& s : m_serverSpecs) { if (s->specName() == name) return s; } return nullptr; } void CClientSocket::onSeq(uint32_t seq, uint32_t id) { for (const auto& c : m_objects) { if (c->m_seq == seq) { c->m_id = id; return; } } Debug::log(WARN, "[{} @ {:.3f}] -> No object for sequence {} (Would be id {}).!", m_fd.get(), steadyMillis(), seq, id); } SP CClientSocket::bindProtocol(const SP& spec, uint32_t version) { if (version > spec->specVer()) { Debug::log(ERR, "version {} is larger than current spec ver of {}", version, spec->specVer()); disconnectOnError(); return nullptr; } auto object = makeShared(m_self.lock()); object->m_spec = spec->objects().front(); object->m_seq = ++m_seq; object->m_version = version; object->m_self = object; object->m_protocolName = spec->specName(); m_objects.emplace_back(object); auto bindMessage = CBindProtocolMessage(spec->specName(), object->m_seq, version); sendMessage(bindMessage); waitForObject(object); return object; } SP CClientSocket::makeObject(const std::string& protocolName, const std::string& objectName, uint32_t seq) { auto object = makeShared(m_self.lock()); object->m_self = object; object->m_protocolName = protocolName; for (const auto& p : m_impls) { if (p->protocol()->specName() != protocolName) continue; for (const auto& o : p->protocol()->objects()) { if (o->objectName() != objectName) continue; object->m_spec = o; break; } break; } if (!object->m_spec) return nullptr; object->m_seq = seq; object->m_version = 0; // TODO: client version doesn't matter that much, but for verification's sake we could fix this m_objects.emplace_back(object); return object; } void CClientSocket::waitForObject(SP x) { m_waitingOnObject = x; while (!x->m_id && !m_error) { dispatchEvents(true); } m_waitingOnObject.reset(); } void CClientSocket::onGeneric(const CGenericProtocolMessage& msg) { for (const auto& o : m_objects) { if (o->m_id == msg.m_object) { o->called(msg.m_method, msg.m_dataSpan, msg.m_fds); return; } } Debug::log(WARN, "[{} @ {:.3f}] -> Generic message not handled. No object with id {}!", m_fd.get(), steadyMillis(), msg.m_object); } SP CClientSocket::objectForId(uint32_t id) { for (const auto& o : m_objects) { if (o->m_id == id) return o; } return nullptr; } SP CClientSocket::objectForSeq(uint32_t seq) { for (const auto& o : m_objects) { if (o->m_seq == seq) return o; } return nullptr; } void CClientSocket::disconnectOnError() { m_error = true; m_fd.reset(); } void CClientSocket::roundtrip() { if (m_error) return; auto nextSeq = ++m_lastSentRoundtripSeq; sendMessage(CRoundtripRequestMessage(nextSeq)); while (m_lastAckdRoundtripSeq < nextSeq) { if (!dispatchEvents(true)) break; } } hyprwm-hyprwire-37bc90e/src/core/client/ClientSocket.hpp000066400000000000000000000063511514062423500234310ustar00rootroot00000000000000#pragma once #include #include #include "../../helpers/Memory.hpp" #include "../socket/SocketHelpers.hpp" #include "../wireObject/IWireObject.hpp" #include #include namespace Hyprwire { class IMessage; class CClientObject; class CGenericProtocolMessage; class CClientSocket : public IClientSocket { public: CClientSocket() = default; virtual ~CClientSocket() = default; bool attempt(const std::string& path); bool attemptFromFd(const int fd); virtual void addImplementation(SP&&); virtual bool dispatchEvents(bool block); virtual int extractLoopFD(); virtual bool waitForHandshake(); virtual SP getSpec(const std::string& name); virtual SP bindProtocol(const SP& spec, uint32_t version); virtual SP objectForId(uint32_t id); virtual SP objectForSeq(uint32_t seq); virtual void roundtrip(); virtual bool isHandshakeDone(); void sendMessage(const IMessage& message); void serverSpecs(const std::vector& s); void recheckPollFds(); void onSeq(uint32_t seq, uint32_t id); void onGeneric(const CGenericProtocolMessage& msg); SP makeObject(const std::string& protocolName, const std::string& objectName, uint32_t seq); void waitForObject(SP); void disconnectOnError(); Hyprutils::OS::CFileDescriptor m_fd; std::vector> m_impls; std::vector> m_serverSpecs; std::vector m_pollfds; std::vector> m_objects; // this is used when waiting on an object WP m_waitingOnObject; std::vector m_pendingOutgoing; // bool m_error = false; bool m_handshakeDone = false; std::chrono::steady_clock::time_point m_handshakeBegin; WP m_self; uint32_t m_seq = 0; uint32_t m_lastAckdRoundtripSeq = 0; uint32_t m_lastSentRoundtripSeq = 0; }; };hyprwm-hyprwire-37bc90e/src/core/client/ServerSpec.hpp000066400000000000000000000012301514062423500231120ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { class CServerSpec : public IProtocolSpec { public: CServerSpec(const std::string& name, uint32_t ver) : m_name(name), m_version(ver) { ; } virtual ~CServerSpec() = default; virtual std::string specName() { return m_name; } virtual uint32_t specVer() { return m_version; } virtual std::vector> objects() { return {}; } std::string m_name; uint32_t m_version = 0; }; };hyprwm-hyprwire-37bc90e/src/core/implementation/000077500000000000000000000000001514062423500220735ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/implementation/Object.cpp000066400000000000000000000014571514062423500240140ustar00rootroot00000000000000#include #include #include #include #include #include #include "../../helpers/Memory.hpp" using namespace Hyprwire; IObject::~IObject() { if (m_onDestroy) m_onDestroy(); } SP IObject::serverSock() { return nullptr; } SP IObject::clientSock() { return nullptr; } void IObject::setData(void* data) { m_data = data; } void* IObject::getData() { return m_data; } void IObject::setOnDestroy(std::function&& fn) { m_onDestroy = std::move(fn); } void IObject::error(uint32_t id, const std::string_view& message) { ; } hyprwm-hyprwire-37bc90e/src/core/implementation/Spec.cpp000066400000000000000000000011031514062423500234640ustar00rootroot00000000000000#include #include #include #include #include using namespace Hyprwire; IProtocolSpec::~IProtocolSpec() = default; IProtocolServerImplementation::~IProtocolServerImplementation() = default; IProtocolClientImplementation::~IProtocolClientImplementation() = default; IServerClient::~IServerClient() = default;hyprwm-hyprwire-37bc90e/src/core/message/000077500000000000000000000000001514062423500204725ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/message/MessageMagic.cpp000066400000000000000000000013041514062423500235210ustar00rootroot00000000000000#include "MessageMagic.hpp" const char* Hyprwire::magicToString(eMessageMagic magic) { switch (magic) { case HW_MESSAGE_MAGIC_END: return "END"; case HW_MESSAGE_MAGIC_TYPE_UINT: return "UINT"; case HW_MESSAGE_MAGIC_TYPE_INT: return "INT"; case HW_MESSAGE_MAGIC_TYPE_F32: return "F32"; case HW_MESSAGE_MAGIC_TYPE_SEQ: return "SEQUENCE"; case HW_MESSAGE_MAGIC_TYPE_OBJECT_ID: return "OBJECT_ID"; case HW_MESSAGE_MAGIC_TYPE_VARCHAR: return "VARCHAR"; case HW_MESSAGE_MAGIC_TYPE_ARRAY: return "ARRAY"; case HW_MESSAGE_MAGIC_TYPE_OBJECT: return "OBJECT"; case HW_MESSAGE_MAGIC_TYPE_FD: return "FD"; } return "ERROR"; } hyprwm-hyprwire-37bc90e/src/core/message/MessageMagic.hpp000066400000000000000000000002121514062423500235230ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { const char* magicToString(eMessageMagic magic); };hyprwm-hyprwire-37bc90e/src/core/message/MessageParser.cpp000066400000000000000000000304001514062423500237340ustar00rootroot00000000000000#include "MessageParser.hpp" #include "MessageType.hpp" #include "../server/ServerClient.hpp" #include "../server/ServerSocket.hpp" #include "../server/ServerObject.hpp" #include "../client/ClientSocket.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/Defines.hpp" #include "../../Macros.hpp" #include "messages/HandshakeAck.hpp" #include "messages/HandshakeBegin.hpp" #include "messages/Hello.hpp" #include "messages/HandshakeProtocols.hpp" #include "messages/BindProtocol.hpp" #include "messages/NewObject.hpp" #include "messages/GenericProtocolMessage.hpp" #include "messages/FatalProtocolError.hpp" #include "messages/RoundtripDone.hpp" #include "messages/RoundtripRequest.hpp" #include #include #include using namespace Hyprwire; eMessageParsingResult CMessageParser::handleMessage(SSocketRawParsedMessage& data, SP client) { size_t needle = 0; while (needle < data.data.size() && !client->m_error) { auto ret = parseSingleMessage(data, needle, client); if (ret == 0) return MESSAGE_PARSED_ERROR; needle += ret; } if (!data.fds.empty()) return MESSAGE_PARSED_STRAY_FDS; TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -- handleMessage: Finished read", client->m_fd.get(), steadyMillis())); return MESSAGE_PARSED_OK; } eMessageParsingResult CMessageParser::handleMessage(SSocketRawParsedMessage& data, SP client) { size_t needle = 0; while (needle < data.data.size()) { auto ret = parseSingleMessage(data, needle, client); if (ret == 0) return MESSAGE_PARSED_ERROR; needle += ret; } if (!data.fds.empty()) return MESSAGE_PARSED_STRAY_FDS; TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -- handleMessage: Finished read", client->m_fd.get(), steadyMillis())); return MESSAGE_PARSED_OK; } size_t CMessageParser::parseSingleMessage(SSocketRawParsedMessage& raw, size_t off, SP client) { auto& data = raw.data; switch (sc(data.at(off))) { case HW_MESSAGE_TYPE_SUP: { auto msg = CHelloMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "client at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_SUP)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->dispatchFirstPoll(); client->sendMessage(CHandshakeBeginMessage(std::vector{HYPRWIRE_PROTOCOL_VER})); return msg.m_len; } case HW_MESSAGE_TYPE_HANDSHAKE_BEGIN: { client->m_error = true; Debug::log(ERR, "client at fd {} core protocol error: invalid message recvd (HANDSHAKE_BEGIN)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_HANDSHAKE_ACK: { auto msg = CHandshakeAckMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "client at fd {} core protocol error: malformed message recvd (HW_MESSAGE_HANDSHAKE_ACK)", client->m_fd.get()); return 0; } client->m_version = msg.m_version; TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); std::vector protocolNames; protocolNames.reserve(client->m_server->m_impls.size()); for (const auto& impl : client->m_server->m_impls) { protocolNames.emplace_back(std::format("{}@{}", impl->protocol()->specName(), impl->protocol()->specVer())); } client->sendMessage(CHandshakeProtocolsMessage(protocolNames)); return msg.m_len; } case HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS: { client->m_error = true; Debug::log(ERR, "client at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_BIND_PROTOCOL: { auto msg = CBindProtocolMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "client at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_BIND_PROTOCOL)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->createObject(msg.m_protocol, "", msg.m_version, msg.m_seq); return msg.m_len; } case HW_MESSAGE_TYPE_NEW_OBJECT: { client->m_error = true; Debug::log(ERR, "client at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_NEW_OBJECT)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE: { auto msg = CGenericProtocolMessage(data, raw.fds, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->onGeneric(msg); return msg.m_len; } case HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR: { client->m_error = true; Debug::log(ERR, "client at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST: { auto msg = CRoundtripRequestMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "client at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->m_scheduledRoundtripSeq = msg.m_seq; return msg.m_len; } case HW_MESSAGE_TYPE_ROUNDTRIP_DONE: { client->m_error = true; Debug::log(ERR, "client at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_ROUNDTRIP_DONE)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_INVALID: break; } Debug::log(ERR, "client at fd {} core protocol error: malformed message recvd (invalid type code)", client->m_fd.get()); client->m_error = true; return 0; } size_t CMessageParser::parseSingleMessage(SSocketRawParsedMessage& raw, size_t off, SP client) { auto& data = raw.data; switch (sc(data.at(off))) { case HW_MESSAGE_TYPE_SUP: { client->m_error = true; Debug::log(ERR, "server at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_SUP)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_HANDSHAKE_BEGIN: { auto msg = CHandshakeBeginMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_HANDSHAKE_BEGIN)", client->m_fd.get()); return 0; } if (!std::ranges::contains(msg.m_versionsSupported, HYPRWIRE_PROTOCOL_VER)) { Debug::log(ERR, "server at fd {} core protocol error: version negotiation failed", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); // version supported: let's select it client->sendMessage(CHandshakeAckMessage(HYPRWIRE_PROTOCOL_VER)); return msg.m_len; } case HW_MESSAGE_TYPE_HANDSHAKE_ACK: { client->m_error = true; Debug::log(ERR, "server at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_HANDSHAKE_ACK)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS: { auto msg = CHandshakeProtocolsMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->serverSpecs(msg.m_protocols); return msg.m_len; } case HW_MESSAGE_TYPE_BIND_PROTOCOL: { client->m_error = true; Debug::log(ERR, "server at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_BIND_PROTOCOL)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_NEW_OBJECT: { auto msg = CNewObjectMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_NEW_OBJECT)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->onSeq(msg.m_seq, msg.m_id); return msg.m_len; } case HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE: { auto msg = CGenericProtocolMessage(data, raw.fds, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE)", client->m_fd.get()); return 0; } client->onGeneric(msg); TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); return msg.m_len; } case HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR: { auto msg = CFatalErrorMessage(data, off); Debug::log(ERR, "fatal protocol error: object {} error {}: {}", msg.m_objectId, msg.m_errorId, msg.m_errorMsg); client->m_error = true; return msg.m_len; } case HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST: { client->m_error = true; Debug::log(ERR, "server at fd {} core protocol error: invalid message recvd (HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST)", client->m_fd.get()); return 0; } case HW_MESSAGE_TYPE_ROUNDTRIP_DONE: { auto msg = CRoundtripDoneMessage(data, off); if (!msg.m_len) { Debug::log(ERR, "server at fd {} core protocol error: malformed message recvd (HW_MESSAGE_TYPE_ROUNDTRIP_DONE)", client->m_fd.get()); return 0; } TRACE(Debug::log(TRACE, "[{} @ {:.3f}] <- {}", client->m_fd.get(), steadyMillis(), msg.parseData())); client->m_lastAckdRoundtripSeq = msg.m_seq; return msg.m_len; } case HW_MESSAGE_TYPE_INVALID: break; } Debug::log(ERR, "server at fd {} core protocol error: invalid message recvd (invalid type code)", client->m_fd.get()); return 0; } std::pair CMessageParser::parseVarInt(const std::vector& data, size_t offset) { return parseVarInt(std::span{&data[offset], data.size() - offset}); } std::pair CMessageParser::parseVarInt(const std::span& data) { size_t rolling = 0; size_t i = 0; const auto LEN = data.size(); do { rolling += ((sc(data[i] << 1) >> 1) << (i * 7)); i++; } while (i < LEN && (data[i - 1] & 0x80)); return {rolling, i}; } std::vector CMessageParser::encodeVarInt(size_t num) { std::vector data; data.resize(4); data[0] = (uint8_t)((num << 25) >> 25) | 0x80; data[1] = (uint8_t)((num << 18) >> 25) | 0x80; data[2] = (uint8_t)((num << 11) >> 25) | 0x80; data[3] = (uint8_t)((num << 4) >> 25) | 0x80; while (data.back() == 0x80 && data.size() > 1) { data.pop_back(); } data.back() &= ~0x80; return data; } hyprwm-hyprwire-37bc90e/src/core/message/MessageParser.hpp000066400000000000000000000024511514062423500237460ustar00rootroot00000000000000#pragma once #include #include #include "../../helpers/Memory.hpp" #include "../socket/SocketHelpers.hpp" namespace Hyprwire { class CServerClient; class CClientSocket; class CGenericProtocolMessage; enum eMessageParsingResult : uint8_t { MESSAGE_PARSED_OK = 0, MESSAGE_PARSED_ERROR = 1, MESSAGE_PARSED_INCOMPLETE = 2, MESSAGE_PARSED_STRAY_FDS = 3, }; class CMessageParser { public: CMessageParser() = default; ~CMessageParser() = default; eMessageParsingResult handleMessage(SSocketRawParsedMessage& data, SP client); eMessageParsingResult handleMessage(SSocketRawParsedMessage& data, SP client); std::pair parseVarInt(const std::vector& data, size_t offset); std::pair parseVarInt(const std::span& data); std::vector encodeVarInt(size_t num); private: size_t parseSingleMessage(SSocketRawParsedMessage& data, size_t off, SP client); size_t parseSingleMessage(SSocketRawParsedMessage& data, size_t off, SP client); }; inline UP g_messageParser = makeUnique(); }; hyprwm-hyprwire-37bc90e/src/core/message/MessageType.hpp000066400000000000000000000056231514062423500234370ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { enum eMessageType : uint8_t { HW_MESSAGE_TYPE_INVALID = 0, /* Sent by the client to initiate the handshake. Params: str -> has to be "VAX" */ HW_MESSAGE_TYPE_SUP = 1, /* Sent by the server after a HELLO. Params: arr(uint) -> versions supported */ HW_MESSAGE_TYPE_HANDSHAKE_BEGIN = 2, /* Sent by the client to confirm a choice of a protocol version Params: uint -> version chosen */ HW_MESSAGE_TYPE_HANDSHAKE_ACK = 3, /* Sent by the server to advertise supported protocols Params: arr(str) -> protocols */ HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS = 4, /* Sent by the client to bind to a specific protocol spec Params: uint -> seq, str -> protocol spec */ HW_MESSAGE_TYPE_BIND_PROTOCOL = 10, /* Sent by the server to acknowledge the bind and return a handle Params: uint -> object handle ID, uint -> seq */ HW_MESSAGE_TYPE_NEW_OBJECT = 11, /* Sent by the server to indicate a fatal protocol error Params: uint -> object handle ID, uint -> error idx, varchar -> error message */ HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR = 12, /* Sent from the client to initiate a roundtrip. Params: uint -> sequence */ HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST = 13, /* Sent from the server to finalize the roundtrip. Params: uint -> sequence */ HW_MESSAGE_TYPE_ROUNDTRIP_DONE = 14, /* Generic protocol message. Can be either direction. Params: uint -> object handle ID, uint -> method ID, data... */ HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE = 100, }; inline const char* messageTypeToStr(eMessageType t) { switch (t) { case HW_MESSAGE_TYPE_INVALID: return "INVALID"; case HW_MESSAGE_TYPE_SUP: return "SUP"; case HW_MESSAGE_TYPE_HANDSHAKE_BEGIN: return "HANDSHAKE_BEGIN"; case HW_MESSAGE_TYPE_HANDSHAKE_ACK: return "HANDSHAKE_ACK"; case HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS: return "HANDSHAKE_PROTOCOLS"; case HW_MESSAGE_TYPE_BIND_PROTOCOL: return "BIND_PROTOCOL"; case HW_MESSAGE_TYPE_NEW_OBJECT: return "NEW_OBJECT"; case HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR: return "HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR"; case HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE: return "GENERIC_PROTOCOL_MESSAGE"; case HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST: return "HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST"; case HW_MESSAGE_TYPE_ROUNDTRIP_DONE: return "HW_MESSAGE_TYPE_ROUNDTRIP_DONE"; } return "ERROR"; } };hyprwm-hyprwire-37bc90e/src/core/message/messages/000077500000000000000000000000001514062423500223015ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/message/messages/BindProtocol.cpp000066400000000000000000000042551514062423500254110ustar00rootroot00000000000000#include "BindProtocol.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include #include using namespace Hyprwire; CBindProtocolMessage::CBindProtocolMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_BIND_PROTOCOL; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_BIND_PROTOCOL) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_seq, &data.at(offset + 2), sizeof(m_seq)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_TYPE_VARCHAR) return; size_t needle = 7; auto [strLen, varIntLen] = g_messageParser->parseVarInt(data, offset + needle); needle += varIntLen; m_protocol = std::string_view{rc(&data.at(offset + needle)), strLen}; needle += strLen; if (data.at(offset + needle) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_version, &data.at(offset + needle + 1), sizeof(m_version)); if (!m_version) return; needle += 5; if (data.at(offset + needle) != HW_MESSAGE_MAGIC_END) return; m_len = needle + 1; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CBindProtocolMessage::CBindProtocolMessage(const std::string& protocol, uint32_t seq, uint32_t version) { m_type = HW_MESSAGE_TYPE_BIND_PROTOCOL; m_data = { HW_MESSAGE_TYPE_BIND_PROTOCOL, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, }; std::memcpy(&m_data[2], &seq, sizeof(seq)); m_data.emplace_back(HW_MESSAGE_MAGIC_TYPE_VARCHAR); m_data.append_range(g_messageParser->encodeVarInt(protocol.length())); m_data.append_range(protocol); m_data.append_range(std::vector{HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0}); std::memcpy(&m_data[m_data.size() - 4], &version, sizeof(version)); m_data.emplace_back(HW_MESSAGE_MAGIC_END); } hyprwm-hyprwire-37bc90e/src/core/message/messages/BindProtocol.hpp000066400000000000000000000007311514062423500254110ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CBindProtocolMessage : public IMessage { public: CBindProtocolMessage(const std::vector& data, size_t offset); CBindProtocolMessage(const std::string& protocol, uint32_t seq, uint32_t version); virtual ~CBindProtocolMessage() = default; std::string m_protocol; uint32_t m_seq = 0, m_version = 0; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/FatalProtocolError.cpp000066400000000000000000000041321514062423500265700ustar00rootroot00000000000000#include "FatalProtocolError.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../wireObject/IWireObject.hpp" #include "../../../helpers/Env.hpp" #include #include #include #include using namespace Hyprwire; CFatalErrorMessage::CFatalErrorMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_objectId, &data.at(offset + 2), sizeof(m_objectId)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_errorId, &data.at(offset + 7), sizeof(m_errorId)); if (data.at(offset + 11) != HW_MESSAGE_MAGIC_TYPE_VARCHAR) return; size_t needle = 12; auto [strLen, strLenLen] = g_messageParser->parseVarInt(data, offset + needle); needle += strLenLen; m_errorMsg = std::string{rc(data.data() + offset + needle), strLen}; needle += strLen; if (data.at(offset + needle) != HW_MESSAGE_MAGIC_END) return; m_len = needle + 1; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CFatalErrorMessage::CFatalErrorMessage(SP obj, uint32_t errorId, const std::string_view& msg) { m_type = HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR; m_data = {HW_MESSAGE_TYPE_FATAL_PROTOCOL_ERROR, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_TYPE_VARCHAR}; if (obj) std::memcpy(&m_data[2], &obj->m_id, sizeof(obj->m_id)); std::memcpy(&m_data[7], &errorId, sizeof(errorId)); m_data.append_range(g_messageParser->encodeVarInt(msg.size())); m_data.append_range(msg); m_data.emplace_back(HW_MESSAGE_MAGIC_END); } hyprwm-hyprwire-37bc90e/src/core/message/messages/FatalProtocolError.hpp000066400000000000000000000010611514062423500265730ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" #include "../../../helpers/Memory.hpp" namespace Hyprwire { class IWireObject; class CFatalErrorMessage : public IMessage { public: CFatalErrorMessage(const std::vector& data, size_t offset); CFatalErrorMessage(SP obj, uint32_t errorId, const std::string_view& msg); virtual ~CFatalErrorMessage() = default; uint32_t m_objectId = 0; uint32_t m_errorId = 0; std::string m_errorMsg; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/GenericProtocolMessage.cpp000066400000000000000000000115411514062423500274120ustar00rootroot00000000000000#include "GenericProtocolMessage.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include "../../../helpers/Log.hpp" #include #include #include using namespace Hyprwire; CGenericProtocolMessage::CGenericProtocolMessage(const std::vector& data, std::vector& fds, size_t offset) { m_type = HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_OBJECT) return; std::memcpy(&m_object, &data.at(offset + 2), sizeof(m_object)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_method, &data.at(offset + 7), sizeof(m_method)); size_t i = 11; while (data.at(offset + i) != HW_MESSAGE_MAGIC_END) { switch (sc(data.at(offset + i))) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: i += 5; break; case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto [a, b] = g_messageParser->parseVarInt(data, offset + i + 1); i += a + b + 1; break; } case HW_MESSAGE_MAGIC_TYPE_ARRAY: { const auto arrType = sc(data.at(offset + i + 1)); auto [arrLen, lenLen] = g_messageParser->parseVarInt(std::span{&data[offset + i + 2], data.size() - i}); size_t arrMessageLen = 2 + lenLen; switch (arrType) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: { arrMessageLen += 4 * arrLen; break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { for (size_t j = 0; j < arrLen; ++j) { auto [strLen, strlenLen] = g_messageParser->parseVarInt(std::span{&data[offset + i + arrMessageLen], data.size() - i - arrMessageLen}); arrMessageLen += strLen + strlenLen; } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { for (size_t j = 0; j < arrLen; ++j) { if (fds.empty()) return; m_fds.emplace_back(fds[0]); fds.erase(fds.begin(), fds.begin() + 1); } i += 0; break; } default: { Debug::log(TRACE, "GenericProtocolMessage: failed demarshaling array message"); return; } } i += arrMessageLen; break; } case HW_MESSAGE_MAGIC_TYPE_FD: { if (fds.empty()) { Debug::log(TRACE, "GenericProtocolMessage: HW_MESSAGE_MAGIC_TYPE_FD but fd queue is empty"); return; } m_fds.emplace_back(fds[0]); fds.erase(fds.begin(), fds.begin() + 1); i += 1; break; } default: { Debug::log(TRACE, "GenericProtocolMessage: failed demarshaling array message"); return; } } } m_len = i + 1; m_dataSpan = std::span{data.begin() + 11 + offset, m_len - 11}; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CGenericProtocolMessage::CGenericProtocolMessage(std::vector&& data, std::vector&& fds) : m_fds(std::move(fds)) { m_data = std::move(data); m_type = HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE; } const std::vector& CGenericProtocolMessage::fds() const { return m_fds; } void CGenericProtocolMessage::resolveSeq(uint32_t id) { m_object = id; if (m_data.size() > 2) m_data[2] = id; } hyprwm-hyprwire-37bc90e/src/core/message/messages/GenericProtocolMessage.hpp000066400000000000000000000014661514062423500274240ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CGenericProtocolMessage : public IMessage { public: CGenericProtocolMessage(const std::vector& data, std::vector& fds, size_t offset); CGenericProtocolMessage(std::vector&& data, std::vector&& fds); virtual ~CGenericProtocolMessage() = default; virtual const std::vector& fds() const; void resolveSeq(uint32_t id); uint32_t m_object = 0; uint32_t m_dependsOnSeq = 0; uint32_t m_method = 0; std::span m_dataSpan; std::vector m_fds; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeAck.cpp000066400000000000000000000026341514062423500253170ustar00rootroot00000000000000#include "HandshakeAck.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CHandshakeAckMessage::CHandshakeAckMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_ACK; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_HANDSHAKE_ACK) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; size_t needle = 2; if (data.at(offset + needle + 4) != HW_MESSAGE_MAGIC_END) return; if ((data.size() - offset - needle) < sizeof(m_version)) return; std::memcpy(&m_version, &data.at(offset + needle), sizeof(m_version)); needle += 4; m_len = needle + 1; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CHandshakeAckMessage::CHandshakeAckMessage(uint32_t version) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_ACK; m_data.reserve(7); m_data = { HW_MESSAGE_TYPE_HANDSHAKE_ACK, HW_MESSAGE_MAGIC_TYPE_UINT, }; m_data.resize(6); std::memcpy(&m_data[2], &version, sizeof(version)); m_data.emplace_back(HW_MESSAGE_MAGIC_END); } hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeAck.hpp000066400000000000000000000006001514062423500253130ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CHandshakeAckMessage : public IMessage { public: CHandshakeAckMessage(const std::vector& data, size_t offset); CHandshakeAckMessage(uint32_t version); virtual ~CHandshakeAckMessage() = default; uint32_t m_version = 0; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeBegin.cpp000066400000000000000000000040071514062423500256410ustar00rootroot00000000000000#include "HandshakeBegin.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CHandshakeBeginMessage::CHandshakeBeginMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_BEGIN; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_HANDSHAKE_BEGIN) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_ARRAY) return; if (data.at(offset + 2) != HW_MESSAGE_MAGIC_TYPE_UINT) return; size_t needle = 3; auto [nVers, varIntLen] = g_messageParser->parseVarInt(data, offset + needle); needle += varIntLen; m_versionsSupported.resize(nVers); for (size_t i = 0; i < nVers; ++i) { std::memcpy(&m_versionsSupported[i], &data.at(offset + needle + (i * sizeof(uint32_t))), sizeof(m_versionsSupported[0])); } needle += (nVers * sizeof(uint32_t)); if (data.at(offset + needle) != HW_MESSAGE_MAGIC_END) return; m_len = needle + 1; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CHandshakeBeginMessage::CHandshakeBeginMessage(const std::vector& versions) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_BEGIN; m_data.reserve(8 + (versions.size() * 4)); m_data = { HW_MESSAGE_TYPE_HANDSHAKE_BEGIN, HW_MESSAGE_MAGIC_TYPE_ARRAY, HW_MESSAGE_MAGIC_TYPE_UINT, }; m_data.append_range(g_messageParser->encodeVarInt(versions.size())); const size_t HEAD_SIZE = m_data.size(); m_data.resize(HEAD_SIZE + (versions.size() * 4)); for (size_t i = 0; i < versions.size(); ++i) { *rc(&m_data.at(HEAD_SIZE + (i * 4))) = versions.at(i); } m_data.emplace_back(HW_MESSAGE_MAGIC_END); } hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeBegin.hpp000066400000000000000000000006601514062423500256470ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CHandshakeBeginMessage : public IMessage { public: CHandshakeBeginMessage(const std::vector& data, size_t offset); CHandshakeBeginMessage(const std::vector& versions); virtual ~CHandshakeBeginMessage() = default; std::vector m_versionsSupported; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeProtocols.cpp000066400000000000000000000037531514062423500266100ustar00rootroot00000000000000#include "HandshakeProtocols.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CHandshakeProtocolsMessage::CHandshakeProtocolsMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_ARRAY) return; if (data.at(offset + 2) != HW_MESSAGE_MAGIC_TYPE_VARCHAR) return; size_t needle = 3; auto [els, varIntLen] = g_messageParser->parseVarInt(data, offset + needle); needle += varIntLen; m_protocols.resize(els); for (size_t i = 0; i < els; ++i) { auto [strLen, strLenLen] = g_messageParser->parseVarInt(data, offset + needle); m_protocols.at(i) = std::string_view{rc(&data.at(offset + needle + strLenLen)), strLen}; needle += strLen + strLenLen; } if (data.at(offset + needle) != HW_MESSAGE_MAGIC_END) return; m_len = needle + 1; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CHandshakeProtocolsMessage::CHandshakeProtocolsMessage(const std::vector& protocols) { m_type = HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS; m_data = { HW_MESSAGE_TYPE_HANDSHAKE_PROTOCOLS, HW_MESSAGE_MAGIC_TYPE_ARRAY, HW_MESSAGE_MAGIC_TYPE_VARCHAR, }; m_data.append_range(g_messageParser->encodeVarInt(protocols.size())); for (const auto& p : protocols) { m_data.append_range(g_messageParser->encodeVarInt(p.size())); m_data.append_range(p); } m_data.emplace_back(HW_MESSAGE_MAGIC_END); } hyprwm-hyprwire-37bc90e/src/core/message/messages/HandshakeProtocols.hpp000066400000000000000000000006771514062423500266170ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CHandshakeProtocolsMessage : public IMessage { public: CHandshakeProtocolsMessage(const std::vector& data, size_t offset); CHandshakeProtocolsMessage(const std::vector& protocols); virtual ~CHandshakeProtocolsMessage() = default; std::vector m_protocols; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/Hello.cpp000066400000000000000000000020371514062423500240520ustar00rootroot00000000000000#include "Hello.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CHelloMessage::CHelloMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_SUP; try { constexpr const std::array expected = { HW_MESSAGE_TYPE_SUP, HW_MESSAGE_MAGIC_TYPE_VARCHAR, 0x03, 'V', 'A', 'X', HW_MESSAGE_MAGIC_END, }; if (std::memcmp(expected.data(), &data.at(offset), expected.size()) != 0) return; m_len = expected.size(); if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CHelloMessage::CHelloMessage() { m_type = HW_MESSAGE_TYPE_SUP; m_data = { HW_MESSAGE_TYPE_SUP, HW_MESSAGE_MAGIC_TYPE_VARCHAR, 0x03, 'V', 'A', 'X', HW_MESSAGE_MAGIC_END, }; } hyprwm-hyprwire-37bc90e/src/core/message/messages/Hello.hpp000066400000000000000000000004631514062423500240600ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CHelloMessage : public IMessage { public: CHelloMessage(const std::vector& data, size_t offset); CHelloMessage(); virtual ~CHelloMessage() = default; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/IMessage.cpp000066400000000000000000000130441514062423500245040ustar00rootroot00000000000000#include "IMessage.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Memory.hpp" #include #include #include #include using namespace Hyprwire; static std::pair formatPrimitiveType(const std::span& s, eMessageMagic type) { switch (type) { case HW_MESSAGE_MAGIC_TYPE_UINT: { if (s.size() < 4) return {"", 0}; uint32_t val = 0; std::memcpy(&val, &s[0], sizeof(val)); return {std::format("{}", val), 4}; } case HW_MESSAGE_MAGIC_TYPE_INT: { if (s.size() < 4) return {"", 0}; int32_t val = 0; std::memcpy(&val, &s[0], sizeof(val)); return {std::format("{}", val), 4}; } case HW_MESSAGE_MAGIC_TYPE_F32: { if (s.size() < 4) return {"", 0}; float val = 0; std::memcpy(&val, &s[0], sizeof(val)); return {std::format("{}", val), 4}; } case HW_MESSAGE_MAGIC_TYPE_FD: { return {"", 0}; } case HW_MESSAGE_MAGIC_TYPE_OBJECT: { if (s.size() < 4) return {"", 0}; uint32_t id = 0; std::memcpy(&id, &s[0], sizeof(id)); return {std::format("object: {}", id == 0 ? "null" : std::to_string(id)), 4}; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto [len, intLen] = g_messageParser->parseVarInt(std::vector(s.data(), s.data() + s.size() - 1), 0); auto ptr = rc(&s[intLen]); return {std::format("\"{}\"", std::string_view{ptr, len}), len + intLen}; } default: break; } return {"", 0}; } std::string IMessage::parseData() const { std::string result; result += messageTypeToStr(m_type); result += " ( "; size_t needle = 1; while (needle < m_data.size()) { switch (sc(m_data.at(needle++))) { case HW_MESSAGE_MAGIC_END: { break; } case HW_MESSAGE_MAGIC_TYPE_SEQ: { int32_t seq = 0; if (m_data.size() - needle >= 4) { std::memcpy(&seq, &m_data.at(needle), 4); result += std::format("seq: {}", seq); needle += 4; } break; } case HW_MESSAGE_MAGIC_TYPE_UINT: { uint32_t val = 0; if (m_data.size() - needle >= 4) { std::memcpy(&val, &m_data.at(needle), 4); result += std::format("{}", val); needle += 4; } break; } case HW_MESSAGE_MAGIC_TYPE_INT: { int32_t val = 0; if (m_data.size() - needle >= 4) { std::memcpy(&val, &m_data.at(needle), 4); result += std::format("{}", val); needle += 4; } break; } case HW_MESSAGE_MAGIC_TYPE_F32: { float val = 0; if (m_data.size() - needle >= 4) { std::memcpy(&val, &m_data.at(needle), 4); result += std::format("{}", val); needle += 4; } break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto [len, intLen] = g_messageParser->parseVarInt(m_data, needle); if (len > 0) { auto ptr = rc(&m_data.at(needle + intLen)); result += std::format("\"{}\"", std::string_view{ptr, len}); } else result += "\"\""; needle += intLen + len; break; } case HW_MESSAGE_MAGIC_TYPE_ARRAY: { auto thisType = sc(m_data.at(needle++)); auto [els, intLen] = g_messageParser->parseVarInt(m_data, needle); result += "{ "; needle += intLen; for (size_t i = 0; i < els; ++i) { auto [str, len] = formatPrimitiveType(std::span{m_data.data() + (needle * sizeof(uint8_t)), m_data.size() - needle}, thisType); needle += len; result += std::format("{}, ", str); } if (els != 0) { result.pop_back(); result.pop_back(); } result += " }"; break; } case HW_MESSAGE_MAGIC_TYPE_OBJECT: { uint32_t id = 0; if (m_data.size() - needle >= 4) { std::memcpy(&id, &m_data.at(needle), 4); needle += 4; result += std::format("object({})", id); } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { result += ""; break; } default: break; } result += ", "; } if (result.at(result.size() - 2) == ',') { result.pop_back(); result.pop_back(); } if (result.at(result.size() - 2) == ',') { result.pop_back(); result.pop_back(); } result += " ) "; return result; } const std::vector& IMessage::fds() const { static const std::vector emptyVec; return emptyVec; } hyprwm-hyprwire-37bc90e/src/core/message/messages/IMessage.hpp000066400000000000000000000010561514062423500245110ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../MessageType.hpp" namespace Hyprwire { class IMessage { public: virtual ~IMessage() = default; virtual const std::vector& fds() const; std::vector m_data; eMessageType m_type = HW_MESSAGE_TYPE_INVALID; size_t m_len = 0; std::string parseData() const; protected: IMessage() = default; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/NewObject.cpp000066400000000000000000000027211514062423500246670ustar00rootroot00000000000000#include "NewObject.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CNewObjectMessage::CNewObjectMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_NEW_OBJECT; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_NEW_OBJECT) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; if ((data.size() - offset - 2) < sizeof(m_seq)) return; std::memcpy(&m_id, &data.at(offset + 2), sizeof(m_id)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_TYPE_UINT) return; std::memcpy(&m_seq, &data.at(offset + 7), sizeof(m_seq)); if (data.at(offset + 11) != HW_MESSAGE_MAGIC_END) return; m_len = 12; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CNewObjectMessage::CNewObjectMessage(uint32_t seq, uint32_t id) { m_type = HW_MESSAGE_TYPE_NEW_OBJECT; m_data = { HW_MESSAGE_TYPE_NEW_OBJECT, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_END, }; std::memcpy(&m_data[2], &id, sizeof(id)); std::memcpy(&m_data[7], &seq, sizeof(seq)); } hyprwm-hyprwire-37bc90e/src/core/message/messages/NewObject.hpp000066400000000000000000000006031514062423500246710ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" namespace Hyprwire { class CNewObjectMessage : public IMessage { public: CNewObjectMessage(const std::vector& data, size_t offset); CNewObjectMessage(uint32_t seq, uint32_t id); virtual ~CNewObjectMessage() = default; uint32_t m_seq = 0, m_id = 0; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/RoundtripDone.cpp000066400000000000000000000024451514062423500256060ustar00rootroot00000000000000#include "RoundtripDone.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../wireObject/IWireObject.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CRoundtripDoneMessage::CRoundtripDoneMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_ROUNDTRIP_DONE; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_ROUNDTRIP_DONE) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; if ((data.size() - offset - 2) < sizeof(m_seq)) return; std::memcpy(&m_seq, &data.at(offset + 2), sizeof(m_seq)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_END) return; m_len = 7; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CRoundtripDoneMessage::CRoundtripDoneMessage(uint32_t seq) : m_seq(seq) { m_type = HW_MESSAGE_TYPE_ROUNDTRIP_DONE; m_data = {HW_MESSAGE_TYPE_ROUNDTRIP_DONE, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_END}; std::memcpy(&m_data[2], &seq, sizeof(seq)); } hyprwm-hyprwire-37bc90e/src/core/message/messages/RoundtripDone.hpp000066400000000000000000000006731514062423500256140ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" #include "../../../helpers/Memory.hpp" namespace Hyprwire { class IWireObject; class CRoundtripDoneMessage : public IMessage { public: CRoundtripDoneMessage(const std::vector& data, size_t offset); CRoundtripDoneMessage(uint32_t seq); virtual ~CRoundtripDoneMessage() = default; uint32_t m_seq = 0; }; };hyprwm-hyprwire-37bc90e/src/core/message/messages/RoundtripRequest.cpp000066400000000000000000000025001514062423500263410ustar00rootroot00000000000000#include "RoundtripRequest.hpp" #include "../MessageType.hpp" #include "../MessageParser.hpp" #include "../../wireObject/IWireObject.hpp" #include "../../../helpers/Env.hpp" #include #include #include using namespace Hyprwire; CRoundtripRequestMessage::CRoundtripRequestMessage(const std::vector& data, size_t offset) { m_type = HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST; try { if (data.at(offset + 0) != HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST) return; if (data.at(offset + 1) != HW_MESSAGE_MAGIC_TYPE_UINT) return; if ((data.size() - offset - 2) < sizeof(m_seq)) return; std::memcpy(&m_seq, &data.at(offset + 2), sizeof(m_seq)); if (data.at(offset + 6) != HW_MESSAGE_MAGIC_END) return; m_len = 7; if (Env::isTrace()) m_data = std::vector{data.begin() + offset, data.begin() + offset + m_len - 1}; } catch (std::out_of_range& e) { m_len = 0; } } CRoundtripRequestMessage::CRoundtripRequestMessage(uint32_t seq) : m_seq(seq) { m_type = HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST; m_data = {HW_MESSAGE_TYPE_ROUNDTRIP_REQUEST, HW_MESSAGE_MAGIC_TYPE_UINT, 0, 0, 0, 0, HW_MESSAGE_MAGIC_END}; std::memcpy(&m_data[2], &seq, sizeof(seq)); } hyprwm-hyprwire-37bc90e/src/core/message/messages/RoundtripRequest.hpp000066400000000000000000000007071514062423500263550ustar00rootroot00000000000000#pragma once #include #include #include "IMessage.hpp" #include "../../../helpers/Memory.hpp" namespace Hyprwire { class IWireObject; class CRoundtripRequestMessage : public IMessage { public: CRoundtripRequestMessage(const std::vector& data, size_t offset); CRoundtripRequestMessage(uint32_t seq); virtual ~CRoundtripRequestMessage() = default; uint32_t m_seq = 0; }; };hyprwm-hyprwire-37bc90e/src/core/server/000077500000000000000000000000001514062423500203545ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/server/ServerClient.cpp000066400000000000000000000115151514062423500234700ustar00rootroot00000000000000#include "ServerClient.hpp" #include "ServerObject.hpp" #include "ServerSocket.hpp" #include "../message/messages/IMessage.hpp" #include "../message/messages/NewObject.hpp" #include "../message/messages/GenericProtocolMessage.hpp" #include "../../helpers/Log.hpp" #include "../../Macros.hpp" #include #include #include #include using namespace Hyprwire; CServerClient::CServerClient(int fd) : m_fd(fd) { m_fd.setFlags(O_CLOEXEC); } CServerClient::~CServerClient() { TRACE(Debug::log(TRACE, "[{}] destroying client", m_fd.get())); } void CServerClient::dispatchFirstPoll() { if (m_firstPollDone) return; m_firstPollDone = true; // get peer's pid #if defined(__OpenBSD__) struct sockpeercred cred; #else ucred cred; #endif socklen_t len = sizeof(cred); if (getsockopt(m_fd.get(), SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { TRACE(Debug::log(TRACE, "dispatchFirstPoll: failed to get pid")); return; } m_pid = cred.pid; } void CServerClient::sendMessage(const IMessage& message) { TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -> {}", m_fd.get(), steadyMillis(), message.parseData())); // NOLINTNEXTLINE msghdr msg = {0}; // NOLINTNEXTLINE iovec io = {0}; const auto& FDS = message.fds(); // fucking evil! io.iov_base = cc(rc(message.m_data.data())); io.iov_len = message.m_data.size(); msg.msg_iov = &io; msg.msg_iovlen = 1; std::vector controlBuf; if (!FDS.empty()) { controlBuf.resize(CMSG_SPACE(sizeof(int) * FDS.size())); msg.msg_control = controlBuf.data(); msg.msg_controllen = controlBuf.size(); cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int) * FDS.size()); int* data = rc(CMSG_DATA(cmsg)); for (size_t i = 0; i < FDS.size(); ++i) { data[i] = FDS.at(i); } } while (m_fd.isValid()) { int ret = sendmsg(m_fd.get(), &msg, 0); if (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { pollfd pfd = { .fd = m_fd.get(), .events = POLLOUT | POLLWRBAND, }; poll(&pfd, 1, -1); } else break; } } SP CServerClient::createObject(const std::string& protocol, const std::string& object, uint32_t version, uint32_t seq) { auto obj = makeShared(m_self.lock()); obj->m_id = m_maxId++; obj->m_self = obj; obj->m_version = version; m_objects.emplace_back(obj); for (const auto& p : m_server->m_impls) { if (p->protocol()->specName() != protocol) continue; for (const auto& s : p->protocol()->objects()) { if (s->objectName() != object && !object.empty()) continue; obj->m_spec = s; break; } obj->m_protocolName = protocol; if (!obj->m_spec) { Debug::log(ERR, "[{} @ {:.3f}] Error: createObject has no spec", m_fd.get(), steadyMillis()); m_error = true; return nullptr; } if (p->protocol()->specVer() < version) { Debug::log(ERR, "[{} @ {:.3f}] Error: createObject for protocol {} object {} for version {}, but we have only {}", m_fd.get(), steadyMillis(), obj->m_protocolName, object, version, p->protocol()->specVer()); m_error = true; return nullptr; } break; } if (!obj->m_spec) { Debug::log(ERR, "[{} @ {:.3f}] Error: createObject has no spec", m_fd.get(), steadyMillis()); m_error = true; return nullptr; } auto ret = CNewObjectMessage(seq, obj->m_id); sendMessage(ret); onBind(obj); return obj; } void CServerClient::onBind(SP obj) { for (const auto& p : m_server->m_impls) { if (p->protocol()->specName() != obj->m_protocolName) continue; for (const auto& on : p->implementation()) { if (on->objectName != obj->m_spec->objectName()) continue; if (on->onBind) on->onBind(obj); break; } break; } } void CServerClient::onGeneric(const CGenericProtocolMessage& msg) { for (const auto& o : m_objects) { if (o->m_id == msg.m_object) { o->called(msg.m_method, msg.m_dataSpan, msg.m_fds); return; } } Debug::log(WARN, "[{} @ {:.3f}] -> Generic message not handled. No object with id {}!", m_fd.get(), steadyMillis(), msg.m_object); } int CServerClient::getPID() { return m_pid; } hyprwm-hyprwire-37bc90e/src/core/server/ServerClient.hpp000066400000000000000000000026331514062423500234760ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../../helpers/Memory.hpp" namespace Hyprwire { class IMessage; class CServerSocket; class CServerObject; class CGenericProtocolMessage; class CServerClient : public IServerClient { public: CServerClient(int fd); virtual ~CServerClient(); virtual int getPID(); void sendMessage(const IMessage& message); SP createObject(const std::string& protocol, const std::string& object, uint32_t version, uint32_t seq); void onBind(SP obj); void onGeneric(const CGenericProtocolMessage& msg); void dispatchFirstPoll(); Hyprutils::OS::CFileDescriptor m_fd; int m_pid = -1; bool m_firstPollDone = false; uint32_t m_version = 0, m_maxId = 1; bool m_error = false; uint32_t m_scheduledRoundtripSeq = 0; std::vector> m_objects; WP m_server; WP m_self; }; };hyprwm-hyprwire-37bc90e/src/core/server/ServerObject.cpp000066400000000000000000000030661514062423500234620ustar00rootroot00000000000000#include "ServerObject.hpp" #include "ServerClient.hpp" #include "ServerSocket.hpp" #include "../../helpers/Log.hpp" #include "../../Macros.hpp" #include "../message/MessageType.hpp" #include "../message/MessageParser.hpp" #include "../message/messages/GenericProtocolMessage.hpp" #include "../message/messages/FatalProtocolError.hpp" #include #include #include #include #include using namespace Hyprwire; CServerObject::CServerObject(SP client) : m_client(client) { ; } CServerObject::~CServerObject() { TRACE(Debug::log(TRACE, "[{}] destroying object {}", m_client->m_fd.get(), m_id)); } const std::vector& CServerObject::methodsOut() { return m_spec->s2c(); } const std::vector& CServerObject::methodsIn() { return m_spec->c2s(); } void CServerObject::errd() { if (m_client) m_client->m_error = true; } void CServerObject::sendMessage(const IMessage& msg) { if (m_client) m_client->sendMessage(msg); } SP CServerObject::client() { return m_client.lock(); } bool CServerObject::server() { return true; } SP CServerObject::self() { return m_self.lock(); } SP CServerObject::serverSock() { if (!m_client || !m_client->m_server) return nullptr; return m_client->m_server.lock(); } void CServerObject::error(uint32_t id, const std::string_view& message) { CFatalErrorMessage msg(m_self.lock(), id, message); m_client->sendMessage(msg); errd(); } hyprwm-hyprwire-37bc90e/src/core/server/ServerObject.hpp000066400000000000000000000023361514062423500234660ustar00rootroot00000000000000#pragma once #include #include #include "ServerClient.hpp" #include "../../helpers/Memory.hpp" #include "../wireObject/IWireObject.hpp" namespace Hyprwire { class CServerClient; class CServerObject : public IWireObject { public: CServerObject(SP client); virtual ~CServerObject(); virtual Hyprutils::Memory::CSharedPointer client(); virtual const std::vector& methodsOut(); virtual const std::vector& methodsIn(); virtual void errd(); virtual void sendMessage(const IMessage&); virtual Hyprutils::Memory::CSharedPointer self(); virtual Hyprutils::Memory::CSharedPointer serverSock(); virtual bool server(); virtual void error(uint32_t id, const std::string_view& message); WP m_client; }; }; hyprwm-hyprwire-37bc90e/src/core/server/ServerSocket.cpp000066400000000000000000000256451514062423500235130ustar00rootroot00000000000000#include "ServerSocket.hpp" #include "ServerClient.hpp" #include "ServerObject.hpp" #include "../../helpers/Memory.hpp" #include "../../helpers/Log.hpp" #include "../../Macros.hpp" #include "../message/MessageParser.hpp" #include "../message/messages/FatalProtocolError.hpp" #include "../message/messages/RoundtripDone.hpp" #include "../socket/SocketHelpers.hpp" #include #include #include #include #include #include #include #include using namespace Hyprwire; using namespace Hyprutils::OS; using namespace Hyprutils::Utils; SP IServerSocket::open(const std::string& path) { SP sock = makeShared(); sock->m_self = sock; if (!sock->attempt(path)) return nullptr; return sock; } SP IServerSocket::open() { SP sock = makeShared(); sock->m_self = sock; if (!sock->attemptEmpty()) return nullptr; return sock; } CServerSocket::CServerSocket() { int pipes[2]; if (pipe(pipes) < 0) Debug::log(ERR, "[- @ {:.3f}] Open wakeup pipes: {}", steadyMillis(), strerror(errno)); else { m_wakeupFd = CFileDescriptor{pipes[0]}; m_wakeupWriteFd = CFileDescriptor{pipes[1]}; m_wakeupWriteFd.setFlags(O_CLOEXEC); m_wakeupFd.setFlags(O_CLOEXEC); } } CServerSocket::~CServerSocket() { if (m_pollThread.joinable()) { m_threadCanPoll = false; m_pollEvent = false; sc(write(m_exitWriteFd.get(), "x", 1)); m_pollEventHandledCV.notify_all(); m_pollThread.join(); } m_fd.reset(); if (!m_path.empty()) { std::error_code ec; std::filesystem::remove(m_path, ec); } } bool CServerSocket::attempt(const std::string& path) { std::error_code ec; if (std::filesystem::exists(path, ec)) { if (ec) return false; // check if the socket is alive, if so, fuck off. m_fd = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM, 0)}; sockaddr_un serverAddress = {.sun_family = AF_UNIX}; if (path.size() >= 108) return false; strcpy(serverAddress.sun_path, path.c_str()); const bool FAILURE = connect(m_fd.get(), (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0; if (!FAILURE) { m_fd.reset(); return false; // alive } if (errno != ECONNREFUSED) return false; // likely alive m_fd.reset(); // remove file and continue std::filesystem::remove(path, ec); if (ec) return false; // no perms? } m_fd = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM, 0)}; sockaddr_un serverAddress = {.sun_family = AF_UNIX}; if (path.size() >= 108) return false; strcpy(serverAddress.sun_path, path.c_str()); if (bind(m_fd.get(), (sockaddr*)&serverAddress, SUN_LEN(&serverAddress))) return false; listen(m_fd.get(), 100); m_fd.setFlags(O_NONBLOCK | O_CLOEXEC); m_path = path; recheckPollFds(); return true; } bool CServerSocket::attemptEmpty() { m_isEmptyListener = true; recheckPollFds(); return true; } void CServerSocket::addImplementation(SP&& x) { m_impls.emplace_back(std::move(x)); } bool CServerSocket::dispatchPending() { poll(m_pollfds.data(), m_pollfds.size(), 0); if (dispatchNewConnections()) return dispatchPending(); return dispatchExistingConnections(); } bool CServerSocket::dispatchEvents(bool block) { m_pollmtx.lock(); while (dispatchPending()) { ; } // read from our event fd to avoid events clearEventFd(); clearWakeupFd(); if (block) { poll(m_pollfds.data(), m_pollfds.size(), -1); while (dispatchPending()) { ; } } m_pollmtx.unlock(); std::unique_lock lk(m_exportPollMtx); m_pollEvent = false; m_pollEventHandledCV.notify_all(); return true; } void CServerSocket::clearFd(const Hyprutils::OS::CFileDescriptor& fd) { char buf[128]; pollfd pfd = { .fd = fd.get(), .events = POLLIN, }; while (fd.isValid()) { poll(&pfd, 1, 0); if (pfd.revents & POLLIN) { sc(read(fd.get(), buf, 127)); continue; } break; } } void CServerSocket::clearEventFd() { clearFd(m_exportFd); } void CServerSocket::clearWakeupFd() { clearFd(m_wakeupFd); } SP CServerSocket::addClient(int fd) { auto x = makeShared(fd); if (x->m_fd.isClosed() || !x->m_fd.isValid()) return nullptr; x->m_self = x; x->m_server = m_self; m_clients.emplace_back(x); recheckPollFds(); // wake up any poller sc(write(m_wakeupWriteFd.get(), "x", 1)); return x; } bool CServerSocket::removeClient(int fd) { auto r = std::erase_if(m_clients, [&fd](const auto& c) { return c->m_fd.get() == fd; }); if (r > 0) recheckPollFds(); return r > 0; } size_t CServerSocket::internalFds() { return m_isEmptyListener ? 2 : 3; } // void CServerSocket::recheckPollFds() { m_pollfds.clear(); if (!m_isEmptyListener) { m_pollfds.emplace_back(pollfd{ .fd = m_fd.get(), .events = POLLIN, }); } m_pollfds.emplace_back(pollfd{ .fd = m_exitFd.get(), .events = POLLIN, }); m_pollfds.emplace_back(pollfd{ .fd = m_wakeupFd.get(), .events = POLLIN, }); for (const auto& c : m_clients) { m_pollfds.emplace_back(pollfd{ .fd = c->m_fd.get(), .events = POLLIN, }); } } bool CServerSocket::dispatchNewConnections() { if (m_isEmptyListener) return false; if (!(m_pollfds.at(0).revents & POLLIN)) return false; sockaddr_in clientAddress = {}; socklen_t clientSize = sizeof(clientAddress); auto x = m_clients.emplace_back(makeShared(accept(m_fd.get(), (sockaddr*)&clientAddress, &clientSize))); x->m_server = m_self; x->m_self = x; recheckPollFds(); return true; } bool CServerSocket::dispatchExistingConnections() { bool hadAny = false; bool needsPollRecheck = false; for (size_t i = internalFds(); i < m_pollfds.size(); ++i) { if (!(m_pollfds.at(i).revents & POLLIN)) continue; dispatchClient(m_clients.at(i - internalFds())); hadAny = true; if (m_pollfds.at(i).revents & POLLHUP) { m_clients.at(i - internalFds())->m_error = true; needsPollRecheck = true; TRACE(Debug::log(TRACE, "[{} @ {:.3f}] Dropping client (hangup)", m_clients.at(i - internalFds())->m_fd.get(), steadyMillis())); continue; } if (m_clients.at(i - internalFds())->m_error) TRACE(Debug::log(TRACE, "[{} @ {:.3f}] Dropping client (protocol error)", m_clients.at(i - internalFds())->m_fd.get(), steadyMillis())); } if (needsPollRecheck) { std::erase_if(m_clients, [](const auto& c) { return c->m_error; }); recheckPollFds(); } return hadAny; } void CServerSocket::dispatchClient(SP client) { auto data = parseFromFd(client->m_fd); if (data.bad) { client->sendMessage(CFatalErrorMessage(nullptr, -1, "fatal: invalid message on wire")); client->m_error = true; return; } if (data.data.empty()) // this should NOT happen return; const auto RET = g_messageParser->handleMessage(data, client); if (RET != MESSAGE_PARSED_OK) { client->sendMessage(CFatalErrorMessage(nullptr, -1, "fatal: failed to handle message on wire")); client->m_error = true; return; } if (client->m_scheduledRoundtripSeq > 0) { client->sendMessage(CRoundtripDoneMessage{client->m_scheduledRoundtripSeq}); client->m_scheduledRoundtripSeq = 0; } } int CServerSocket::extractLoopFD() { if (!m_exportFd.isValid()) { int pipes[2]; if (pipe(pipes) < 0) { Debug::log(ERR, "[- @ {:.3f}] Failed to export pipes for poll thread: {}", steadyMillis(), strerror(errno)); return -1; } m_exportFd = CFileDescriptor{pipes[0]}; m_exportWriteFd = CFileDescriptor{pipes[1]}; m_exportFd.setFlags(O_CLOEXEC); m_exportWriteFd.setFlags(O_CLOEXEC); if (pipe(pipes) < 0) { Debug::log(ERR, "[- @ {:.3f}] Failed to exit pipes for poll thread: {}", steadyMillis(), strerror(errno)); return -1; } m_exitFd = CFileDescriptor{pipes[0]}; m_exitWriteFd = CFileDescriptor{pipes[1]}; m_exitFd.setFlags(O_CLOEXEC); m_exitWriteFd.setFlags(O_CLOEXEC); m_threadCanPoll = true; recheckPollFds(); m_pollThread = std::thread([this] { while (m_threadCanPoll) { m_pollmtx.lock(); std::vector pollfds; if (!m_isEmptyListener) { pollfds.emplace_back(pollfd{ .fd = m_fd.get(), .events = POLLIN, }); } pollfds.emplace_back(pollfd{ .fd = m_exitFd.get(), .events = POLLIN, }); pollfds.emplace_back(pollfd{ .fd = m_wakeupFd.get(), .events = POLLIN, }); for (const auto& c : m_clients) { pollfds.emplace_back(pollfd{ .fd = c->m_fd.get(), .events = POLLIN, }); } m_pollmtx.unlock(); poll(pollfds.data(), pollfds.size(), -1); if (!m_threadCanPoll) return; { std::unique_lock lk(m_exportPollMtx); m_pollEvent = true; sc(write(m_exportWriteFd.get(), "x", 1)); m_pollEventHandledCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return !m_pollEvent; }); } } }); } return m_exportFd.get(); } SP CServerSocket::createObject(SP clientIface, SP reference, const std::string& object, uint32_t seq) { if (!clientIface || !reference) return nullptr; auto client = reinterpretPointerCast(clientIface); auto ref = reinterpretPointerCast(reference); auto newObject = client->createObject(ref->m_protocolName, object, ref->m_version, seq); if (!newObject) return nullptr; return newObject; } hyprwm-hyprwire-37bc90e/src/core/server/ServerSocket.hpp000066400000000000000000000057511514062423500235140ustar00rootroot00000000000000#pragma once #include #include #include "../../helpers/Memory.hpp" #include #include #include #include #include namespace Hyprwire { class CServerClient; class CServerSocket : public IServerSocket { public: CServerSocket(); virtual ~CServerSocket(); bool attempt(const std::string& path); bool attemptEmpty(); virtual void addImplementation(SP&&); virtual bool dispatchEvents(bool block); virtual int extractLoopFD(); virtual SP createObject(SP client, SP reference, const std::string& object, uint32_t seq); virtual SP addClient(int fd); virtual bool removeClient(int fd); void recheckPollFds(); bool dispatchNewConnections(); bool dispatchExistingConnections(); bool dispatchPending(); void dispatchClient(SP client); void clearEventFd(); void clearWakeupFd(); void clearFd(const Hyprutils::OS::CFileDescriptor& fd); size_t internalFds(); std::vector> m_impls; Hyprutils::OS::CFileDescriptor m_fd; Hyprutils::OS::CFileDescriptor m_exportFd, m_exportWriteFd; Hyprutils::OS::CFileDescriptor m_exitFd, m_exitWriteFd; Hyprutils::OS::CFileDescriptor m_wakeupFd, m_wakeupWriteFd; std::vector m_pollfds; std::vector> m_clients; WP m_self; bool m_threadCanPoll = false; std::thread m_pollThread; std::recursive_mutex m_pollmtx; std::mutex m_exportPollMtx; std::condition_variable m_pollEventHandledCV; bool m_pollEvent = false; bool m_isEmptyListener = false; std::string m_path; }; }; hyprwm-hyprwire-37bc90e/src/core/socket/000077500000000000000000000000001514062423500203365ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/socket/SocketHelpers.cpp000066400000000000000000000042761514062423500236260ustar00rootroot00000000000000#include "SocketHelpers.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/Memory.hpp" #include "../../Macros.hpp" #include #include #include #include using namespace Hyprwire; SSocketRawParsedMessage Hyprwire::parseFromFd(const Hyprutils::OS::CFileDescriptor& fd) { SSocketRawParsedMessage message; constexpr size_t BUFFER_SIZE = 8192; constexpr size_t MAX_FDS_PER_MSG = 255; static uint8_t buffer[BUFFER_SIZE] = {0}; static std::mutex readMutex; // TODO: make the buffer per-socket and no need for a mtx std::lock_guard lg(readMutex); ssize_t sizeWritten = 0; do { // NOLINTNEXTLINE msghdr msg = {0}; // NOLINTNEXTLINE iovec io = {0}; io.iov_base = buffer; io.iov_len = BUFFER_SIZE; msg.msg_iov = &io; msg.msg_iovlen = 1; std::array controlBuf; msg.msg_control = controlBuf.data(); msg.msg_controllen = controlBuf.size(); sizeWritten = recvmsg(fd.get(), &msg, 0); if (sizeWritten < 0) return {.bad = true}; message.data.append_range(std::span(buffer, sizeWritten)); // check for control cmsghdr* recvdCmsg = CMSG_FIRSTHDR(&msg); if (!recvdCmsg) continue; if (recvdCmsg->cmsg_level != SOL_SOCKET || recvdCmsg->cmsg_type != SCM_RIGHTS) { Debug::log(ERR, "protocol error on fd {}: invalid control message on wire of type {}", fd.get(), recvdCmsg->cmsg_type); return {.bad = true}; } int* data = rc(CMSG_DATA(recvdCmsg)); size_t payloadSize = recvdCmsg->cmsg_len - CMSG_LEN(0); size_t numFds = payloadSize / sizeof(int); message.fds.reserve(message.fds.size() + numFds); for (size_t i = 0; i < numFds; ++i) { message.fds.emplace_back(data[i]); } TRACE(Debug::log(TRACE, "parseFromFd: got {} fds on the control wire", numFds)); } while (sizeWritten == BUFFER_SIZE); return message; } hyprwm-hyprwire-37bc90e/src/core/socket/SocketHelpers.hpp000066400000000000000000000005461514062423500236270ustar00rootroot00000000000000#pragma once #include #include #include namespace Hyprwire { struct SSocketRawParsedMessage { std::vector data; std::vector fds; bool bad = false; }; SSocketRawParsedMessage parseFromFd(const Hyprutils::OS::CFileDescriptor& fd); }; hyprwm-hyprwire-37bc90e/src/core/wireObject/000077500000000000000000000000001514062423500211435ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/core/wireObject/IWireObject.cpp000066400000000000000000000473611514062423500240300ustar00rootroot00000000000000#include "IWireObject.hpp" #include "../../Macros.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/FFI.hpp" #include "../client/ClientObject.hpp" #include "../message/MessageType.hpp" #include "../message/MessageParser.hpp" #include "../message/MessageMagic.hpp" #include "../message/messages/GenericProtocolMessage.hpp" #include #include #include #include #include #include using namespace Hyprwire; using namespace Hyprutils::Utils; IWireObject::~IWireObject() = default; uint32_t IWireObject::call(uint32_t id, ...) { const auto METHODS = methodsOut(); if (METHODS.size() <= id) { const auto MSG = std::format("core protocol error: invalid method {} for object {}", id, m_id); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return 0; } va_list va; va_start(va, id); const auto& method = METHODS.at(id); const auto params = method.params; if (method.since > m_version) { const auto MSG = std::format("method {} since {} but has {}", id, method.since, m_version); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return 0; } if (!method.returnsType.empty() && server()) { const auto MSG = std::format("invalid method spec {} for object {} -> server cannot call returnsType methods", id, m_id); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return 0; } // encode the message std::vector data; std::vector fds; data.emplace_back(HW_MESSAGE_TYPE_GENERIC_PROTOCOL_MESSAGE); data.emplace_back(HW_MESSAGE_MAGIC_TYPE_OBJECT); data.resize(data.size() + 4); std::memcpy(&data[data.size() - 4], &m_id, sizeof(m_id)); data.emplace_back(HW_MESSAGE_MAGIC_TYPE_UINT); data.resize(data.size() + 4); std::memcpy(&data[data.size() - 4], &id, sizeof(id)); size_t returnSeq = 0; if (!method.returnsType.empty()) { if (Env::isTrace()) { auto selfClient = reinterpretPointerCast(m_self.lock()); TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -- call {}: returnsType has {}", selfClient->m_client->m_fd.get(), steadyMillis(), id, method.returnsType)); } data.emplace_back(HW_MESSAGE_MAGIC_TYPE_SEQ); data.resize(data.size() + 4); auto selfClient = reinterpretPointerCast(m_self.lock()); uint32_t seqVal = ++selfClient->m_client->m_seq; std::memcpy(&data[data.size() - 4], &seqVal, sizeof(seqVal)); returnSeq = selfClient->m_client->m_seq; } for (size_t i = 0; i < params.size(); ++i) { switch (sc(params.at(i))) { case HW_MESSAGE_MAGIC_TYPE_UINT: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_UINT); data.resize(data.size() + 4); uint32_t val = va_arg(va, uint32_t); std::memcpy(&data[data.size() - 4], &val, sizeof(val)); break; } case HW_MESSAGE_MAGIC_TYPE_INT: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_INT); data.resize(data.size() + 4); int32_t val = va_arg(va, int32_t); std::memcpy(&data[data.size() - 4], &val, sizeof(val)); break; } case HW_MESSAGE_MAGIC_TYPE_OBJECT: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_OBJECT); data.resize(data.size() + 4); uint32_t val = va_arg(va, uint32_t); std::memcpy(&data[data.size() - 4], &val, sizeof(val)); break; } case HW_MESSAGE_MAGIC_TYPE_F32: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_F32); data.resize(data.size() + 4); float val = va_arg(va, double); std::memcpy(&data[data.size() - 4], &val, sizeof(val)); break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_VARCHAR); auto str = va_arg(va, const char*); data.append_range(g_messageParser->encodeVarInt(std::string_view(str).size())); data.append_range(std::string_view(str)); break; } case HW_MESSAGE_MAGIC_TYPE_ARRAY: { const auto arrType = sc(params.at(++i)); data.emplace_back(HW_MESSAGE_MAGIC_TYPE_ARRAY); data.emplace_back(arrType); auto arrayData = va_arg(va, void*); auto arrayLen = va_arg(va, uint32_t); data.append_range(g_messageParser->encodeVarInt(arrayLen)); switch (arrType) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_OBJECT: { for (size_t j = 0; j < arrayLen; ++j) { data.resize(data.size() + 4); uint32_t val = rc(arrayData)[j]; std::memcpy(&data[data.size() - 4], &val, sizeof(val)); } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { for (size_t j = 0; j < arrayLen; ++j) { fds.emplace_back(rc(arrayData)[j]); } break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { for (size_t i = 0; i < arrayLen; ++i) { const char* element = rc(arrayData)[i]; data.append_range(g_messageParser->encodeVarInt(std::string_view(element).size())); data.append_range(std::string_view(element)); } break; } default: { Debug::log(ERR, "core protocol error: failed marshaling array type"); errd(); return 0; } } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { data.emplace_back(HW_MESSAGE_MAGIC_TYPE_FD); // add fd to our message fds.emplace_back(va_arg(va, int32_t)); break; } default: break; } } data.emplace_back(HW_MESSAGE_MAGIC_END); auto msg = CGenericProtocolMessage(std::move(data), std::move(fds)); if (!m_id && !server()) { auto selfClient = reinterpretPointerCast(m_self.lock()); TRACE(Debug::log(TRACE, "[{} @ {:.3f}] -- call: waiting on object of type {}", selfClient->m_client->m_fd.get(), steadyMillis(), method.returnsType)); msg.m_dependsOnSeq = m_seq; selfClient->m_client->m_pendingOutgoing.emplace_back(std::move(msg)); if (returnSeq) { selfClient->m_client->makeObject(m_protocolName, method.returnsType, returnSeq); return returnSeq; } } else { sendMessage(msg); if (returnSeq) { // we are a client auto selfClient = reinterpretPointerCast(m_self.lock()); selfClient->m_client->makeObject(m_protocolName, method.returnsType, returnSeq); return returnSeq; } } return 0; } void IWireObject::listen(uint32_t id, void* fn) { if (m_listeners.size() <= id) m_listeners.resize(id + 1); m_listeners.at(id) = fn; } void IWireObject::called(uint32_t id, const std::span& data, const std::vector& fds) { const auto METHODS = methodsIn(); if (METHODS.size() <= id) { const auto MSG = std::format("invalid method {} for object {}", id, m_id); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } if (m_listeners.size() <= id || m_listeners.at(id) == nullptr) return; const auto& method = METHODS.at(id); std::vector params; if (!method.returnsType.empty()) params.emplace_back(HW_MESSAGE_MAGIC_TYPE_SEQ); params.append_range(method.params); if (method.since > m_version) { const auto MSG = std::format("method {} since {} but has {}", id, method.since, m_version); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } std::vector ffiTypes = {&ffi_type_pointer}; size_t dataI = 0; for (size_t i = 0; i < params.size(); ++i) { const auto PARAM = sc(params.at(i)); const auto WIRE_PARAM = sc(data[dataI]); if (PARAM != WIRE_PARAM) { // raise protocol error const auto MSG = std::format("method {} param idx {} should be {} but was {}", id, i, magicToString(PARAM), magicToString(WIRE_PARAM)); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } auto ffiType = FFI::ffiTypeFrom(PARAM); ffiTypes.emplace_back(ffiType); switch (PARAM) { case HW_MESSAGE_MAGIC_END: ++i; break; // BUG if this happens or malformed message case HW_MESSAGE_MAGIC_TYPE_FD: dataI++; break; case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: dataI += 5; break; case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto [a, b] = g_messageParser->parseVarInt(std::span{&data[dataI + 1], data.size() - dataI}); dataI += a + b + 1; break; } case HW_MESSAGE_MAGIC_TYPE_ARRAY: { const auto arrType = sc(params.at(++i)); const auto wireType = sc(data[dataI + 1]); if (arrType != wireType) { // raise protocol error const auto MSG = std::format("method {} param idx {} should be {} but was {}", id, i, magicToString(PARAM), magicToString(WIRE_PARAM)); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } auto [arrLen, lenLen] = g_messageParser->parseVarInt(std::span{&data[dataI + 2], data.size() - i}); size_t arrMessageLen = 2 + lenLen; ffiTypes.emplace_back(FFI::ffiTypeFrom(HW_MESSAGE_MAGIC_TYPE_UINT /* length */)); switch (arrType) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: { arrMessageLen += 4 * arrLen; break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { for (size_t j = 0; j < arrLen; ++j) { if (dataI + arrMessageLen > data.size()) { const auto MSG = std::format("failed demarshaling array message"); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } auto [strLen, strlenLen] = g_messageParser->parseVarInt(std::span{&data[dataI + arrMessageLen], data.size() - dataI - arrMessageLen}); arrMessageLen += strLen + strlenLen; } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { break; } default: { const auto MSG = std::format("failed demarshaling array message"); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } } dataI += arrMessageLen; break; } case HW_MESSAGE_MAGIC_TYPE_OBJECT_ID: { const auto MSG = std::format("object type is not impld"); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } } } ffi_cif cif; if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, ffiTypes.size(), &ffi_type_void, ffiTypes.data())) { Debug::log(ERR, "core protocol error: ffi failed"); errd(); return; } std::vector avalues, otherBuffers; avalues.reserve(ffiTypes.size()); std::vector> strings; auto ptrBuf = malloc(sizeof(IObject*)); avalues.emplace_back(ptrBuf); *rc(ptrBuf) = m_self.get(); size_t fdNo = 0; CScopeGuard x([&] { for (const auto& v : avalues) { free(v); } for (const auto& v : otherBuffers) { free(v); } }); for (size_t i = 0; i < data.size(); ++i) { void* buf = nullptr; const auto PARAM = sc(data[i]); // FIXME: add type checking if (PARAM == HW_MESSAGE_MAGIC_END) break; switch (PARAM) { case HW_MESSAGE_MAGIC_END: break; case HW_MESSAGE_MAGIC_TYPE_UINT: { buf = malloc(sizeof(uint32_t)); std::memcpy(buf, &data[i + 1], sizeof(uint32_t)); i += 4; break; } case HW_MESSAGE_MAGIC_TYPE_F32: { buf = malloc(sizeof(float)); std::memcpy(buf, &data[i + 1], sizeof(float)); i += 4; break; } case HW_MESSAGE_MAGIC_TYPE_INT: { buf = malloc(sizeof(int32_t)); std::memcpy(buf, &data[i + 1], sizeof(int32_t)); i += 4; break; } case HW_MESSAGE_MAGIC_TYPE_OBJECT: { buf = malloc(sizeof(uint32_t)); std::memcpy(buf, &data[i + 1], sizeof(uint32_t)); i += 4; break; } case HW_MESSAGE_MAGIC_TYPE_SEQ: { buf = malloc(sizeof(uint32_t)); std::memcpy(buf, &data[i + 1], sizeof(uint32_t)); i += 4; break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto [strLen, len] = g_messageParser->parseVarInt(std::span{&data[i + 1], data.size() - i - 1}); buf = malloc(sizeof(const char*)); auto& str = strings.emplace_back(makeShared(std::string_view{rc(&data[i + len + 1]), strLen})); *rc(buf) = str->c_str(); i += strLen + len; break; } case HW_MESSAGE_MAGIC_TYPE_ARRAY: { const auto arrType = sc(data[i + 1]); auto [arrLen, lenLen] = g_messageParser->parseVarInt(std::span{&data[i + 2], data.size() - i}); size_t arrMessageLen = 2 + lenLen; switch (arrType) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_F32: case HW_MESSAGE_MAGIC_TYPE_INT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: { auto dataPtr = rc(malloc(sizeof(uint32_t) * (arrLen == 0 ? 1 : arrLen))); auto dataSlot = rc(malloc(sizeof(uint32_t**))); auto sizeSlot = rc(malloc(sizeof(uint32_t))); *dataSlot = dataPtr; *sizeSlot = arrLen; avalues.emplace_back(dataSlot); avalues.emplace_back(sizeSlot); otherBuffers.emplace_back(dataPtr); for (size_t j = 0; j < arrLen; ++j) { std::memcpy(&dataPtr[j], &data[i + arrMessageLen], sizeof(uint32_t)); arrMessageLen += 4; } break; } case HW_MESSAGE_MAGIC_TYPE_VARCHAR: { auto dataPtr = rc(malloc(sizeof(const char*) * (arrLen == 0 ? 1 : arrLen))); auto dataSlot = rc(malloc(sizeof(const char***))); auto sizeSlot = rc(malloc(sizeof(uint32_t))); *dataSlot = dataPtr; *sizeSlot = arrLen; avalues.emplace_back(dataSlot); avalues.emplace_back(sizeSlot); otherBuffers.emplace_back(dataPtr); for (size_t j = 0; j < arrLen; ++j) { auto [strLen, strlenLen] = g_messageParser->parseVarInt(std::span{&data[i + arrMessageLen], data.size() - i}); auto& str = strings.emplace_back(makeShared(std::string_view{rc(&data[i + arrMessageLen + strlenLen]), strLen})); dataPtr[j] = str->c_str(); arrMessageLen += strlenLen + strLen; } break; } case HW_MESSAGE_MAGIC_TYPE_FD: { auto dataPtr = rc(malloc(sizeof(int32_t) * (arrLen == 0 ? 1 : arrLen))); auto dataSlot = rc(malloc(sizeof(int32_t**))); auto sizeSlot = rc(malloc(sizeof(uint32_t))); *dataSlot = dataPtr; *sizeSlot = arrLen; avalues.emplace_back(dataSlot); avalues.emplace_back(sizeSlot); otherBuffers.emplace_back(dataPtr); for (size_t j = 0; j < arrLen; ++j) { dataPtr[j] = fds.at(fdNo++); } break; } default: { const auto MSG = std::format("failed demarshaling array message"); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } } i += arrMessageLen - 1 /* for loop does ++i*/; break; } case HW_MESSAGE_MAGIC_TYPE_OBJECT_ID: { const auto MSG = std::format("object type is not impld"); Debug::log(ERR, "core protocol error: {}", MSG); error(m_id, MSG); return; } case HW_MESSAGE_MAGIC_TYPE_FD: { buf = malloc(sizeof(int32_t)); *rc(buf) = fds.at(fdNo++); break; } } if (buf) avalues.emplace_back(buf); } auto fptr = reinterpret_cast(m_listeners.at(id)); ffi_call(&cif, fptr, nullptr, avalues.data()); } hyprwm-hyprwire-37bc90e/src/core/wireObject/IWireObject.hpp000066400000000000000000000025241514062423500240250ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../../helpers/Memory.hpp" namespace Hyprwire { class IMessage; class IWireObject : public IObject { public: virtual ~IWireObject(); virtual uint32_t call(uint32_t id, ...); virtual void listen(uint32_t id, void* fn); virtual void called(uint32_t id, const std::span& data, const std::vector& fds); virtual const std::vector& methodsOut() = 0; virtual const std::vector& methodsIn() = 0; virtual void errd() = 0; virtual void sendMessage(const IMessage&) = 0; virtual bool server() = 0; std::vector m_listeners; uint32_t m_id = 0, m_version = 0, m_seq = 1; std::string m_protocolName; SP m_spec; WP m_self; protected: IWireObject() = default; }; };hyprwm-hyprwire-37bc90e/src/helpers/000077500000000000000000000000001514062423500175605ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/src/helpers/Defines.hpp000066400000000000000000000001611514062423500216440ustar00rootroot00000000000000#pragma once #include namespace Hyprwire { constexpr const uint32_t HYPRWIRE_PROTOCOL_VER = 1; } hyprwm-hyprwire-37bc90e/src/helpers/Env.cpp000066400000000000000000000006541514062423500210210ustar00rootroot00000000000000#include "Env.hpp" #include #include using namespace Hyprwire; using namespace Hyprwire::Env; bool Hyprwire::Env::envEnabled(const std::string& env) { auto ret = getenv(env.c_str()); if (!ret) return false; const std::string_view sv = ret; return !sv.empty() && sv != "0"; } bool Hyprwire::Env::isTrace() { static bool TRACE = envEnabled("HW_TRACE"); return TRACE; }hyprwm-hyprwire-37bc90e/src/helpers/Env.hpp000066400000000000000000000001761514062423500210250ustar00rootroot00000000000000#pragma once #include namespace Hyprwire::Env { bool envEnabled(const std::string& env); bool isTrace(); } hyprwm-hyprwire-37bc90e/src/helpers/FFI.cpp000066400000000000000000000011521514062423500206670ustar00rootroot00000000000000#include "FFI.hpp" using namespace Hyprwire; ffi_type* Hyprwire::FFI::ffiTypeFrom(eMessageMagic magic) { switch (magic) { case HW_MESSAGE_MAGIC_TYPE_UINT: case HW_MESSAGE_MAGIC_TYPE_OBJECT: case HW_MESSAGE_MAGIC_TYPE_SEQ: return &ffi_type_uint32; case HW_MESSAGE_MAGIC_TYPE_FD: case HW_MESSAGE_MAGIC_TYPE_INT: return &ffi_type_sint32; case HW_MESSAGE_MAGIC_TYPE_F32: return &ffi_type_float; case HW_MESSAGE_MAGIC_TYPE_VARCHAR: case HW_MESSAGE_MAGIC_TYPE_ARRAY: return &ffi_type_pointer; default: return nullptr; } return nullptr; }hyprwm-hyprwire-37bc90e/src/helpers/FFI.hpp000066400000000000000000000002341514062423500206740ustar00rootroot00000000000000#pragma once #include #include namespace Hyprwire::FFI { ffi_type* ffiTypeFrom(eMessageMagic magic); } hyprwm-hyprwire-37bc90e/src/helpers/Log.hpp000066400000000000000000000016441514062423500210170ustar00rootroot00000000000000#pragma once #include #include #include enum eLogLevel : int8_t { NONE = -1, LOG = 0, WARN, ERR, CRIT, INFO, TRACE }; namespace Debug { inline bool trace = false; template void log(eLogLevel level, std::format_string fmt, Args&&... args) { if (!trace && (level == LOG || level == INFO)) return; switch (level) { case NONE: break; case LOG: std::cout << "[hw] log: "; break; case WARN: std::cout << "[hw] warn: "; break; case ERR: std::cout << "[hw] err: "; break; case CRIT: std::cout << "[hw] crit: "; break; case INFO: std::cout << "[hw] info: "; break; case TRACE: std::cout << "[hw] trace: "; break; } std::cout << std::vformat(fmt.get(), std::make_format_args(args...)) << std::endl; } }; hyprwm-hyprwire-37bc90e/src/helpers/Memory.hpp000066400000000000000000000004351514062423500215430ustar00rootroot00000000000000#pragma once #include #include #include using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer #define ASP CAtomicSharedPointerhyprwm-hyprwire-37bc90e/tests/000077500000000000000000000000001514062423500164715ustar00rootroot00000000000000hyprwm-hyprwire-37bc90e/tests/Client.cpp000066400000000000000000000050421514062423500204140ustar00rootroot00000000000000#include #include #include "generated/test_protocol_v1-client.hpp" #include using namespace Hyprutils::Memory; #define SP CSharedPointer constexpr const uint32_t TEST_PROTOCOL_VERSION = 1; static SP impl = makeShared(TEST_PROTOCOL_VERSION); static SP manager; static SP object; static SP sock; int main(int argc, char** argv, char** envp) { const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR"); sock = Hyprwire::IClientSocket::open(XDG_RUNTIME_DIR + std::string{"/test-hw.sock"}); if (!sock) { std::println("err: failed to open client socket"); return 1; } sock->addImplementation(impl); if (!sock->waitForHandshake()) { std::println("err: handshake failed"); return 1; } const auto SPEC = sock->getSpec(impl->protocol()->specName()); if (!SPEC) { std::println("err: test protocol unsupported"); return 1; } std::println("test protocol supported at version {}. Binding.", SPEC->specVer()); manager = makeShared(sock->bindProtocol(impl->protocol(), TEST_PROTOCOL_VERSION)); std::println("Bound!"); int pips[2]; sc(pipe(pips)); sc(write(pips[1], "pipe!", 5)); std::println("Will send fd {}", pips[0]); int pips2[2]; int pips3[2]; sc(pipe(pips2)); sc(pipe(pips3)); sc(write(pips2[1], "o kurwa", 7)); sc(write(pips3[1], "bober!!", 7)); manager->sendSendMessage("Hello!"); manager->sendSendMessageFd(pips[0]); manager->sendSendMessageArrayFd(std::vector{pips2[0], pips3[0]}); manager->sendSendMessageArray(std::vector{"Hello", "via", "array!"}); manager->sendSendMessageArray(std::vector{}); manager->sendSendMessageArrayUint(std::vector{69, 420, 2137}); manager->setSendMessage([](const char* msg) { std::println("Server says {}", msg); }); // test roundtrip sock->roundtrip(); object = makeShared(manager->sendMakeObject()); object->setSendMessage([](const char* msg) { std::println("Server says on object {}", msg); }); object->sendSendMessage("Hello on object"); object->sendSendEnum(TEST_PROTOCOL_V1_MY_ENUM_WORLD); std::println("Sent hello!"); while (sock->dispatchEvents(true)) { ; } return 0; } hyprwm-hyprwire-37bc90e/tests/Fork.cpp000066400000000000000000000145351514062423500201060ustar00rootroot00000000000000#include #include #include #include #include #include "generated/test_protocol_v1-server.hpp" #include "generated/test_protocol_v1-client.hpp" using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer constexpr const uint32_t TEST_PROTOCOL_VERSION = 1; static bool quitt = false; static void sigHandler(int sig) { quitt = true; } static SP manager; static std::vector> objects; static SP serverSock; static SP impl = makeShared(TEST_PROTOCOL_VERSION); static void makeObject(uint32_t seq) { auto object = makeShared(serverSock->createObject(manager->getObject()->client(), manager->getObject(), "my_object_v1", seq)); object->sendSendMessage("Hello object"); object->setMakeObject(makeObject); object->setSendMessage([](const char* msg) { std::println("Object says hello: {}", msg); }); object->setSendEnum([wobj = WP{object}](testProtocolV1MyEnum e) { std::println("Object sent enum: {}", sc(e)); std::println("Erroring out the client!"); quitt = true; if (wobj) wobj->error(TEST_PROTOCOL_V1_MY_ERROR_ENUM_ERROR_IMPORTANT, "Important error occurred!"); }); objects.emplace_back(std::move(object)); } static SP spec = makeShared(TEST_PROTOCOL_VERSION, [](SP obj) { std::println("Object bound XD"); manager = makeShared(std::move(obj)); manager->sendSendMessage("Hello manager"); manager->setSendMessage([](const char* msg) { std::println("Recvd message: {}", msg); }); manager->setSendMessageFd([](int fd) { char msgbuf[6] = {0}; sc(read(fd, msgbuf, 5)); std::println("Recvd fd {} with data: {}", fd, msgbuf); }); manager->setSendMessageArray([](std::vector data) { std::string conct = ""; for (const auto& d : data) { conct += d + std::string{", "}; } if (conct.size() > 1) { conct.pop_back(); conct.pop_back(); } std::println("Got array message: \"{}\"", conct); }); manager->setSendMessageArrayUint([](std::vector data) { std::string conct = ""; for (const auto& d : data) { conct += std::format("{}, ", d); } conct.pop_back(); conct.pop_back(); std::println("Got uint array message: \"{}\"", conct); }); manager->setMakeObject(makeObject); manager->setOnDestroy([w = WP{manager}]() { // std::println("object {:x} destroyed", (uintptr_t)manager.get()); }); }); static void server(int clientFd) { serverSock = Hyprwire::IServerSocket::open(); pollfd pfd = {.fd = clientFd, .events = POLLIN, .revents = 0}; serverSock->addImplementation(spec); // This is not requried, but it is nice to have if (!(poll(&pfd, 1, 1000) > 0 && (pfd.revents & POLLIN))) { std::println("Failed to wait for client hello"); exit(1); } if (serverSock->addClient(clientFd) == nullptr) { std::println("Failed to add clientFd to the server socket!"); exit(1); } signal(SIGINT, ::sigHandler); signal(SIGTERM, ::sigHandler); pfd = {.fd = serverSock->extractLoopFD(), .events = POLLIN, .revents = 0}; while (!quitt) { int events = poll(&pfd, 1, -1); if (events < 0) { if (errno == EAGAIN) continue; else break; } else if (events == 0) continue; if (pfd.revents & POLLHUP) break; if (!(pfd.revents & POLLIN)) continue; serverSock->dispatchEvents(false); } } static void client(int serverFd) { auto sock = Hyprwire::IClientSocket::open(serverFd); if (!sock->waitForHandshake()) { std::println("err: handshake failed"); return; } sock->addImplementation(impl); std::println("OK!"); const auto SPEC = sock->getSpec(impl->protocol()->specName()); if (!SPEC) { std::println("err: test protocol unsupported"); return; } std::println("test protocol supported at version {}. Binding.", SPEC->specVer()); auto cmanager = makeShared(sock->bindProtocol(impl->protocol(), TEST_PROTOCOL_VERSION)); std::println("Bound!"); int pips[2]; sc(pipe(pips)); sc(write(pips[1], "pipe!", 5)); std::println("Will send fd {}", pips[0]); cmanager->sendSendMessage("Hello!"); cmanager->sendSendMessageFd(pips[0]); cmanager->sendSendMessageArray(std::vector{"Hello", "via", "array!"}); cmanager->sendSendMessageArray(std::vector{}); cmanager->sendSendMessageArrayUint(std::vector{69, 420, 2137}); cmanager->setSendMessage([](const char* msg) { std::println("Server says {}", msg); }); auto cobject = makeShared(cmanager->sendMakeObject()); auto cobject2 = makeShared(cobject->sendMakeObject()); cobject->setSendMessage([&cobject](const char* msg) { std::println("Server says on object {}", msg); }); cobject2->setSendMessage([&cobject](const char* msg) { std::println("Server says on object2 {}", msg); cobject->sendSendEnum(TEST_PROTOCOL_V1_MY_ENUM_WORLD); quitt = true; }); cobject->sendSendMessage("Hello from object"); cobject2->sendSendMessage("Hello from object2"); while (!quitt) sock->dispatchEvents(true); sock->roundtrip(); } int main(int argc, char** argv, char** envp) { int sockFds[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockFds)) return 1; int s = 0; int c = 1; pid_t chld = fork(); if (chld < 0) { close(sockFds[s]); close(sockFds[c]); std::println(stderr, "Failed to fork"); } else if (chld == 0) { // CHILD (Client) close(sockFds[s]); client(sockFds[c]); } else { // PARENT (Server) close(sockFds[c]); server(sockFds[s]); } return 0; } hyprwm-hyprwire-37bc90e/tests/Server.cpp000066400000000000000000000062401514062423500204450ustar00rootroot00000000000000#include #include #include #include #include "generated/test_protocol_v1-server.hpp" using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer static SP manager; static SP object; static SP sock; static SP spec = makeShared(1, [](SP obj) { std::println("Object bound XD"); manager = makeShared(std::move(obj)); manager->sendSendMessage("Hello manager"); manager->setSendMessage([](const char* msg) { std::println("Recvd message: {}", msg); }); manager->setSendMessageFd([](int fd) { char msgbuf[6] = {0}; sc(read(fd, msgbuf, 5)); std::println("Recvd fd {} with data: {}", fd, msgbuf); }); manager->setSendMessageArrayFd([](const std::vector& fds) { std::println("Received {} fds", fds.size()); for (int fd : fds) { char msgbuf[8] = {0}; sc(read(fd, msgbuf, 7)); std::println("fd {} with data: {}", fd, msgbuf); } }); manager->setSendMessageArray([](std::vector data) { std::string conct = ""; for (const auto& d : data) { conct += d + std::string{", "}; } if (conct.size() > 1) { conct.pop_back(); conct.pop_back(); } std::println("Got array message: \"{}\"", conct); }); manager->setSendMessageArrayUint([](std::vector data) { std::string conct = ""; for (const auto& d : data) { conct += std::format("{}, ", d); } conct.pop_back(); conct.pop_back(); std::println("Got uint array message: \"{}\"", conct); }); manager->setMakeObject([](uint32_t seq) { object = makeShared(sock->createObject(manager->getObject()->client(), manager->getObject(), CMyObjectV1Object::name(), seq)); object->sendSendMessage("Hello object"); object->setSendMessage([](const char* msg) { std::println("Object says hello: {}", msg); }); object->setSendEnum([](testProtocolV1MyEnum e) { std::println("Object sent enum: {}", sc(e)); std::println("Erroring out the client!"); object->error(TEST_PROTOCOL_V1_MY_ERROR_ENUM_ERROR_IMPORTANT, "Important error occurred!"); }); }); manager->setOnDestroy([w = WP{manager}]() { // std::println("object {:x} destroyed", (uintptr_t)manager.get()); }); }); static bool quitt = false; static void sigHandler(int sig) { quitt = true; } int main(int argc, char** argv, char** envp) { const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR"); sock = Hyprwire::IServerSocket::open(XDG_RUNTIME_DIR + std::string{"/test-hw.sock"}); sock->addImplementation(spec); signal(SIGINT, ::sigHandler); signal(SIGTERM, ::sigHandler); while (!quitt && sock->dispatchEvents(true)) { ; } return 0; } hyprwm-hyprwire-37bc90e/tests/protocol-v1.xml000066400000000000000000000070311514062423500214010ustar00rootroot00000000000000 I eat paint This object is an example manager object for the protocol Sends a text message to the client Sends a text message to the server Sends a fd message to the server Sends a fd message to the server Sends an array message to the server Sends an array message to the server Receives an array message to the server Makes a test object This object is an example object for the protocol Sends a text message to the client Sends a text message to the server Sends an enum message to the server Sends a text message to the server Makes a test object from a test object