Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Rethil
aquamarine
aquamarine-0.3.3.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File aquamarine-0.3.3.obscpio of Package aquamarine
07070100000000000081A400000000000000000000000166C38D3F0000070E000000000000000000000000000000000000001F00000000aquamarine-0.3.3/.clang-format--- 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 07070100000001000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001900000000aquamarine-0.3.3/.github07070100000002000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002300000000aquamarine-0.3.3/.github/workflows07070100000003000081A400000000000000000000000166C38D3F0000025B000000000000000000000000000000000000002B00000000aquamarine-0.3.3/.github/workflows/nix.ymlname: Build & Test on: [push, pull_request, workflow_dispatch] jobs: nix: strategy: matrix: package: - aquamarine # - aquamarine-with-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: cachix/install-nix-action@v26 - uses: DeterminateSystems/magic-nix-cache-action@main # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build & Test run: nix build .#${{ matrix.package }} --print-build-logs 07070100000004000081A400000000000000000000000166C38D3F00000147000000000000000000000000000000000000001C00000000aquamarine-0.3.3/.gitignore# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app build/ .vscode/ .cache/ protocols/*.cpp protocols/*.hpp07070100000005000081A400000000000000000000000166C38D3F0000118B000000000000000000000000000000000000002000000000aquamarine-0.3.3/CMakeLists.txtcmake_minimum_required(VERSION 3.19) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} AQUAMARINE_VERSION) add_compile_definitions(AQUAMARINE_VERSION="${AQUAMARINE_VERSION}") project( aquamarine VERSION ${AQUAMARINE_VERSION} DESCRIPTION "A very light linux rendering backend library") include(CTest) include(CheckIncludeFile) include(GNUInstallDirs) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED COMPONENTS "GLES2") find_package(hyprwayland-scanner 0.4.0 REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET libseat>=0.8.0 libinput>=1.26.0 wayland-client wayland-protocols hyprutils>=0.1.5 pixman-1 libdrm gbm libudev libdisplay-info hwdata) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) set(CMAKE_CXX_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring aquamarine in Debug") add_compile_definitions(AQUAMARINE_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring aquamarine in Release") endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") add_library(aquamarine SHARED ${SRCFILES}) target_include_directories( aquamarine PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") set_target_properties(aquamarine PROPERTIES VERSION ${AQUAMARINE_VERSION} SOVERSION 2) target_link_libraries(aquamarine OpenGL::EGL OpenGL::OpenGL 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(aquamarine PkgConfig::epoll) endif() # Protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) message(STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}") function(protocolNew protoPath protoName external) if(external) set(path ${CMAKE_SOURCE_DIR}/${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(aquamarine PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) endfunction() function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(aquamarine PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() protocolwayland() protocolnew("stable/xdg-shell" "xdg-shell" false) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) # Generate hwdata info pkg_get_variable(HWDATA_DIR hwdata pkgdatadir) message( STATUS "Running ${CMAKE_SOURCE_DIR}/data/hwdata.sh < ${HWDATA_DIR}/pnp.ids") execute_process( COMMAND /bin/sh -c "${CMAKE_SOURCE_DIR}/data/hwdata.sh < ${HWDATA_DIR}/pnp.ids" RESULT_VARIABLE HWDATA_PNP_RESULT OUTPUT_VARIABLE HWDATA_PNP_IDS ENCODING UTF8) if(NOT HWDATA_PNP_RESULT MATCHES 0) message(WARNING "hwdata gathering pnps failed") endif() configure_file(data/hwdata.hpp.in hwdata.hpp @ONLY) # tests add_custom_target(tests) add_executable(simpleWindow "tests/SimpleWindow.cpp") target_link_libraries(simpleWindow PRIVATE PkgConfig::deps aquamarine) add_test( NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow") add_dependencies(tests simpleWindow) # Installation install(TARGETS aquamarine) install(DIRECTORY "include/aquamarine" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_BINARY_DIR}/aquamarine.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 07070100000006000081A400000000000000000000000166C38D3F000005DF000000000000000000000000000000000000001900000000aquamarine-0.3.3/LICENSEBSD 3-Clause License Copyright (c) 2024, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 07070100000007000081A400000000000000000000000166C38D3F000003A2000000000000000000000000000000000000001B00000000aquamarine-0.3.3/README.md## Aquamarine Aquamarine is a very light linux rendering backend library. It provides basic abstractions for an application to render on a Wayland session (in a window) or a native DRM session. It is agnostic of the rendering API (Vulkan/OpenGL) and designed to be lightweight, performant, and minimal. Aquamarine provides no bindings for other languages. It is C++-only. ## Stability Aquamarine depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for aquamarine ABI breaks, not stdlib. ## Building ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` ``` ## TODOs - [x] Wayland backend - [x] DRM backend (DRM / KMS / libinput) - [x] Virtual backend (aka. Headless) - [ ] Hardware plane support 07070100000008000081A400000000000000000000000166C38D3F00000006000000000000000000000000000000000000001900000000aquamarine-0.3.3/VERSION0.3.3 07070100000009000081A400000000000000000000000166C38D3F00000100000000000000000000000000000000000000002200000000aquamarine-0.3.3/aquamarine.pc.inprefix=@PREFIX@ includedir=@INCLUDE@ libdir=@LIBDIR@ Name: aquamarine URL: https://github.com/hyprwm/aquamarine Description: A very light linux rendering backend library Version: @AQUAMARINE_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -laquamarine 0707010000000A000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001600000000aquamarine-0.3.3/data0707010000000B000081A400000000000000000000000166C38D3F000000C1000000000000000000000000000000000000002400000000aquamarine-0.3.3/data/hwdata.hpp.in#include <unordered_map> #include <string> #define __AQ_PNP_PROP(pnp, manu) {pnp, manu} inline std::unordered_map<std::string, std::string> PNPIDS = { @HWDATA_PNP_IDS@ }; #undef __AQ_PNP_PROP 0707010000000C000081ED00000000000000000000000166C38D3F00000087000000000000000000000000000000000000002000000000aquamarine-0.3.3/data/hwdata.sh#!/bin/sh while read -r id vendor; do [ "${#id}" = 3 ] || exit 1 printf "\t__AQ_PNP_PROP(\"%s\", \"%s\"),\n" "$id" "$vendor" done 0707010000000D000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001600000000aquamarine-0.3.3/docs0707010000000E000081A400000000000000000000000166C38D3F000001E1000000000000000000000000000000000000001D00000000aquamarine-0.3.3/docs/env.md## Environment variables Unless specified otherwise, a variable is enabled if and only if it's set to `1` ### DRM `AQ_DRM_DEVICES` -> Set an explicit list of DRM devices (GPUs) to use. It's a colon-separated list of paths, with the first being the primary. E.g. `/dev/dri/card1:/dev/dri/card0` `AQ_NO_ATOMIC` -> Disables drm atomic modesetting `AQ_MGPU_NO_EXPLICIT` -> Disables explicit syncing on mgpu buffers ### Debugging `AQ_TRACE` -> Enables trace (very verbose) logging 0707010000000F000081A400000000000000000000000166C38D3F000008B3000000000000000000000000000000000000001C00000000aquamarine-0.3.3/flake.lock{ "nodes": { "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1721324102, "narHash": "sha256-WAZ0X6yJW1hFG6otkHBfyJDKRpNP5stsRqdEuHrFRpk=", "owner": "hyprwm", "repo": "hyprutils", "rev": "962582a090bc233c4de9d9897f46794280288989", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1721324119, "narHash": "sha256-SOOqIT27/X792+vsLSeFdrNTF+OSRp5qXv6Te+fb2Qg=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "a048a6cb015340bd82f97c1f40a4b595ca85cc30", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1721138476, "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=", "owner": "NixOS", "repo": "nixpkgs", "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "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 } 07070100000010000081A400000000000000000000000166C38D3F00000A38000000000000000000000000000000000000001B00000000aquamarine-0.3.3/flake.nix{ description = "A very light linux rendering backend library"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; }; outputs = { self, nixpkgs, systems, ... } @ inputs: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [aquamarine]; }); pkgsCrossFor = eachSystem (system: crossSystem: import nixpkgs { localSystem = system; crossSystem = crossSystem; overlays = with self.overlays; [aquamarine]; }); 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 { overlays = { default = self.overlays.aquamarine; aquamarine = lib.composeManyExtensions [ self.overlays.libinput inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default (final: prev: { aquamarine = final.callPackage ./nix/default.nix { stdenv = final.gcc13Stdenv; version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); }; aquamarine-with-tests = final.aquamarine.override {doCheck = true;}; }) ]; libinput = final: prev: { libinput = prev.libinput.overrideAttrs (self: super: { version = "1.26.0"; src = final.fetchFromGitLab { domain = "gitlab.freedesktop.org"; owner = "libinput"; repo = "libinput"; rev = self.version; hash = "sha256-mlxw4OUjaAdgRLFfPKMZDMOWosW9yKAkzDccwuLGCwQ="; }; }); }; }; packages = eachSystem (system: { default = self.packages.${system}.aquamarine; inherit (pkgsFor.${system}) aquamarine aquamarine-with-tests; aquamarine-cross = (pkgsCrossFor.${system} "aarch64-linux").aquamarine; }); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } 07070100000011000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001900000000aquamarine-0.3.3/include07070100000012000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002400000000aquamarine-0.3.3/include/aquamarine07070100000013000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002E00000000aquamarine-0.3.3/include/aquamarine/allocator07070100000014000081A400000000000000000000000166C38D3F0000055E000000000000000000000000000000000000003C00000000aquamarine-0.3.3/include/aquamarine/allocator/Allocator.hpp#pragma once #include <hyprutils/memory/SharedPtr.hpp> #include "../buffer/Buffer.hpp" #include <drm_fourcc.h> namespace Aquamarine { class CBackend; class CSwapchain; struct SAllocatorBufferParams { Hyprutils::Math::Vector2D size; uint32_t format = DRM_FORMAT_INVALID; bool scanout = false, cursor = false, multigpu = false; }; enum eAllocatorType { AQ_ALLOCATOR_TYPE_GBM = 0, }; class IAllocator { public: virtual ~IAllocator() = default; virtual Hyprutils::Memory::CSharedPointer<IBuffer> acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain) = 0; virtual Hyprutils::Memory::CSharedPointer<CBackend> getBackend() = 0; virtual int drmFD() = 0; virtual eAllocatorType type() = 0; }; }; 07070100000015000081A400000000000000000000000166C38D3F00000A72000000000000000000000000000000000000003600000000aquamarine-0.3.3/include/aquamarine/allocator/GBM.hpp#pragma once #include "Allocator.hpp" struct gbm_device; struct gbm_bo; namespace Aquamarine { class CGBMAllocator; class CBackend; class CSwapchain; class CGBMBuffer : public IBuffer { public: virtual ~CGBMBuffer(); virtual eBufferCapability caps(); virtual eBufferType type(); virtual void update(const Hyprutils::Math::CRegion& damage); virtual bool isSynchronous(); virtual bool good(); virtual SDMABUFAttrs dmabuf(); virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags); virtual void endDataPtr(); private: CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer<CGBMAllocator> allocator_, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain); Hyprutils::Memory::CWeakPointer<CGBMAllocator> allocator; // gbm stuff gbm_bo* bo = nullptr; void* boBuffer = nullptr; void* gboMapping = nullptr; SDMABUFAttrs attrs{.success = false}; friend class CGBMAllocator; }; class CGBMAllocator : public IAllocator { public: ~CGBMAllocator(); static Hyprutils::Memory::CSharedPointer<CGBMAllocator> create(int drmfd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_); virtual Hyprutils::Memory::CSharedPointer<IBuffer> acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain_); virtual Hyprutils::Memory::CSharedPointer<CBackend> getBackend(); virtual int drmFD(); virtual eAllocatorType type(); // Hyprutils::Memory::CWeakPointer<CGBMAllocator> self; private: CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_); // a vector purely for tracking (debugging) the buffers and nothing more std::vector<Hyprutils::Memory::CWeakPointer<CGBMBuffer>> buffers; int fd = -1; Hyprutils::Memory::CWeakPointer<CBackend> backend; // gbm stuff gbm_device* gbmDevice = nullptr; std::string gbmDeviceBackendName = ""; std::string drmName = ""; friend class CGBMBuffer; friend class CDRMRenderer; }; }; 07070100000016000081A400000000000000000000000166C38D3F00000910000000000000000000000000000000000000003C00000000aquamarine-0.3.3/include/aquamarine/allocator/Swapchain.hpp#pragma once #include "Allocator.hpp" namespace Aquamarine { class IBackendImplementation; struct SSwapchainOptions { size_t length = 0; Hyprutils::Math::Vector2D size; uint32_t format = DRM_FORMAT_INVALID; // if you leave this on invalid, the swapchain will choose an appropriate format (and modifier) for you. bool scanout = false, cursor = false /* requires scanout = true */, multigpu = false /* if true, will force linear */; }; class CSwapchain { public: static Hyprutils::Memory::CSharedPointer<CSwapchain> create(Hyprutils::Memory::CSharedPointer<IAllocator> allocator_, Hyprutils::Memory::CSharedPointer<IBackendImplementation> backendImpl_); bool reconfigure(const SSwapchainOptions& options_); bool contains(Hyprutils::Memory::CSharedPointer<IBuffer> buffer); Hyprutils::Memory::CSharedPointer<IBuffer> next(int* age); const SSwapchainOptions& currentOptions(); Hyprutils::Memory::CSharedPointer<IAllocator> getAllocator(); // rolls the buffers back, marking the last consumed as the next valid. // useful if e.g. a commit fails and we don't wanna write to the previous buffer that is // in use. void rollback(); private: CSwapchain(Hyprutils::Memory::CSharedPointer<IAllocator> allocator_, Hyprutils::Memory::CSharedPointer<IBackendImplementation> backendImpl_); bool fullReconfigure(const SSwapchainOptions& options_); bool resize(size_t newSize); // Hyprutils::Memory::CWeakPointer<CSwapchain> self; SSwapchainOptions options; Hyprutils::Memory::CSharedPointer<IAllocator> allocator; Hyprutils::Memory::CWeakPointer<IBackendImplementation> backendImpl; std::vector<Hyprutils::Memory::CSharedPointer<IBuffer>> buffers; int lastAcquired = 0; friend class CGBMBuffer; }; }; 07070100000017000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002C00000000aquamarine-0.3.3/include/aquamarine/backend07070100000018000081A400000000000000000000000166C38D3F000019AE000000000000000000000000000000000000003800000000aquamarine-0.3.3/include/aquamarine/backend/Backend.hpp#pragma once #include <hyprutils/memory/SharedPtr.hpp> #include <hyprutils/signal/Signal.hpp> #include <vector> #include <functional> #include <mutex> #include <condition_variable> #include "../allocator/Allocator.hpp" #include "Misc.hpp" #include "Session.hpp" namespace Aquamarine { enum eBackendType : uint32_t { AQ_BACKEND_WAYLAND = 0, AQ_BACKEND_DRM, AQ_BACKEND_HEADLESS, }; enum eBackendRequestMode : uint32_t { /* Require the provided backend, will error out if it's not available. */ AQ_BACKEND_REQUEST_MANDATORY = 0, /* Start the backend if it's available */ AQ_BACKEND_REQUEST_IF_AVAILABLE, /* If any IF_AVAILABLE backend fails, use this one */ AQ_BACKEND_REQUEST_FALLBACK, }; enum eBackendLogLevel : uint32_t { AQ_LOG_TRACE = 0, AQ_LOG_DEBUG, AQ_LOG_WARNING, AQ_LOG_ERROR, AQ_LOG_CRITICAL, }; struct SBackendImplementationOptions { explicit SBackendImplementationOptions(); eBackendType backendType; eBackendRequestMode backendRequestMode; }; struct SBackendOptions { explicit SBackendOptions(); std::function<void(eBackendLogLevel, std::string)> logFunction; }; struct SPollFD { int fd = -1; std::function<void(void)> onSignal; /* call this when signaled */ }; class IBackendImplementation { public: virtual ~IBackendImplementation() { ; } enum eBackendCapabilities : uint32_t { AQ_BACKEND_CAPABILITY_POINTER = (1 << 0), }; virtual eBackendType type() = 0; virtual bool start() = 0; virtual std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> pollFDs() = 0; virtual int drmFD() = 0; virtual bool dispatchEvents() = 0; virtual uint32_t capabilities() = 0; virtual void onReady() = 0; virtual std::vector<SDRMFormat> getRenderFormats() = 0; virtual std::vector<SDRMFormat> getCursorFormats() = 0; virtual bool createOutput(const std::string& name = "") = 0; // "" means auto virtual Hyprutils::Memory::CSharedPointer<IAllocator> preferredAllocator() = 0; virtual std::vector<SDRMFormat> getRenderableFormats(); // empty = use getRenderFormats }; class CBackend { public: /* Create a backend, with the provided options. May return a single or a multi-backend. */ static Hyprutils::Memory::CSharedPointer<CBackend> create(const std::vector<SBackendImplementationOptions>& backends, const SBackendOptions& options); ~CBackend(); /* start the backend. Initializes all the stuff, and will return true on success, false on fail. */ bool start(); void log(eBackendLogLevel level, const std::string& msg); /* Gets all the FDs you have to poll. When any single one fires, call its onPoll */ std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> getPollFDs(); /* Checks if the backend has a session - iow if it's a DRM backend */ bool hasSession(); /* Get the primary DRM FD */ int drmFD(); /* Get the render formats the primary backend supports */ std::vector<SDRMFormat> getPrimaryRenderFormats(); /* get a vector of the backend implementations available */ const std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>>& getImplementations(); /* push an idle event to the queue */ void addIdleEvent(Hyprutils::Memory::CSharedPointer<std::function<void(void)>> fn); /* remove an idle event from the queue */ void removeIdleEvent(Hyprutils::Memory::CSharedPointer<std::function<void(void)>> pfn); // utils int reopenDRMNode(int drmFD, bool allowRenderNode = true); struct { Hyprutils::Signal::CSignal newOutput; Hyprutils::Signal::CSignal newPointer; Hyprutils::Signal::CSignal newKeyboard; Hyprutils::Signal::CSignal newTouch; Hyprutils::Signal::CSignal newSwitch; Hyprutils::Signal::CSignal newTablet; Hyprutils::Signal::CSignal newTabletTool; Hyprutils::Signal::CSignal newTabletPad; } events; Hyprutils::Memory::CSharedPointer<IAllocator> primaryAllocator; bool ready = false; Hyprutils::Memory::CSharedPointer<CSession> session; private: CBackend(); bool terminate = false; std::vector<SBackendImplementationOptions> implementationOptions; std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations; SBackendOptions options; Hyprutils::Memory::CWeakPointer<CBackend> self; std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> sessionFDs; struct { int fd = -1; std::vector<Hyprutils::Memory::CSharedPointer<std::function<void(void)>>> pending; } idle; void dispatchIdle(); void updateIdleTimer(); // struct { std::condition_variable loopSignal; std::mutex loopMutex; std::atomic<bool> shouldProcess = false; std::mutex loopRequestMutex; std::mutex eventLock; } m_sEventLoopInternals; }; }; 07070100000019000081A400000000000000000000000166C38D3F000047E1000000000000000000000000000000000000003400000000aquamarine-0.3.3/include/aquamarine/backend/DRM.hpp#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" #include <hyprutils/memory/WeakPtr.hpp> #include <wayland-client.h> #include <xf86drmMode.h> namespace Aquamarine { class CDRMBackend; class CDRMFB; class CDRMOutput; struct SDRMConnector; class CDRMRenderer; typedef std::function<void(void)> FIdleCallback; class CDRMBufferAttachment : public IAttachment { public: CDRMBufferAttachment(Hyprutils::Memory::CSharedPointer<CDRMFB> fb_); virtual ~CDRMBufferAttachment() { ; } virtual eAttachmentType type() { return AQ_ATTACHMENT_DRM_BUFFER; } Hyprutils::Memory::CSharedPointer<CDRMFB> fb; }; class CDRMBufferUnimportable : public IAttachment { public: CDRMBufferUnimportable() { ; } virtual ~CDRMBufferUnimportable() { ; } virtual eAttachmentType type() { return AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE; } }; class CDRMLease { public: static Hyprutils::Memory::CSharedPointer<CDRMLease> create(std::vector<Hyprutils::Memory::CSharedPointer<IOutput>> outputs); ~CDRMLease(); void terminate(); int leaseFD = -1; uint32_t lesseeID = 0; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; std::vector<Hyprutils::Memory::CWeakPointer<CDRMOutput>> outputs; bool active = true; struct { Hyprutils::Signal::CSignal destroy; } events; private: CDRMLease() = default; void destroy(); friend class CDRMBackend; }; class CDRMFB { public: ~CDRMFB(); static Hyprutils::Memory::CSharedPointer<CDRMFB> create(Hyprutils::Memory::CSharedPointer<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, bool* isNew = nullptr); void closeHandles(); // drops the buffer from KMS void drop(); // re-imports the buffer into KMS. Essentially drop and import. void reimport(); uint32_t id = 0; Hyprutils::Memory::CWeakPointer<IBuffer> buffer; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; std::array<uint32_t, 4> boHandles = {0, 0, 0, 0}; // true if the original buffer is gone and this has been released. bool dead = false; private: CDRMFB(Hyprutils::Memory::CSharedPointer<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_); uint32_t submitBuffer(); void import(); bool dropped = false, handlesClosed = false; struct { Hyprutils::Signal::CHyprSignalListener destroyBuffer; } listeners; }; struct SDRMLayer { // we expect the consumers to use double-buffering, so we keep the 2 last FBs around. If any of these goes out of // scope, the DRM FB will be destroyed, but the IBuffer will stay, as long as it's ref'd somewhere. Hyprutils::Memory::CSharedPointer<CDRMFB> front /* currently displaying */, back /* submitted */, last /* keep just in case */; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; }; struct SDRMPlane { bool init(drmModePlane* plane); uint64_t type = 0; uint32_t id = 0; uint32_t initialID = 0; Hyprutils::Memory::CSharedPointer<CDRMFB> front /* currently displaying */, back /* submitted */, last /* keep just in case */; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; Hyprutils::Memory::CWeakPointer<SDRMPlane> self; std::vector<SDRMFormat> formats; union UDRMPlaneProps { struct { uint32_t type; uint32_t rotation; // Not guaranteed to exist uint32_t in_formats; // Not guaranteed to exist // atomic-modesetting only uint32_t src_x; uint32_t src_y; uint32_t src_w; uint32_t src_h; uint32_t crtc_x; uint32_t crtc_y; uint32_t crtc_w; uint32_t crtc_h; uint32_t fb_id; uint32_t crtc_id; uint32_t fb_damage_clips; uint32_t hotspot_x; uint32_t hotspot_y; uint32_t in_fence_fd; }; uint32_t props[17] = {0}; }; UDRMPlaneProps props; }; struct SDRMCRTC { uint32_t id = 0; std::vector<SDRMLayer> layers; int32_t refresh = 0; // unused struct { int gammaSize = 0; } legacy; struct { bool ownModeID = false; uint32_t modeID = 0; uint32_t gammaLut = 0; } atomic; Hyprutils::Memory::CSharedPointer<SDRMPlane> primary; Hyprutils::Memory::CSharedPointer<SDRMPlane> cursor; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; Hyprutils::Memory::CSharedPointer<CDRMFB> pendingCursor; union UDRMCRTCProps { struct { // None of these are guaranteed to exist uint32_t vrr_enabled; uint32_t gamma_lut; uint32_t gamma_lut_size; // atomic-modesetting only uint32_t active; uint32_t mode_id; uint32_t out_fence_ptr; }; uint32_t props[7] = {0}; }; UDRMCRTCProps props; }; class CDRMOutput : public IOutput { public: virtual ~CDRMOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipShedule = false); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual void setCursorVisible(bool visible); virtual Hyprutils::Math::Vector2D cursorPlaneSize(); virtual size_t getGammaSize(); virtual std::vector<SDRMFormat> getRenderFormats(); int getConnectorID(); Hyprutils::Memory::CWeakPointer<CDRMOutput> self; Hyprutils::Memory::CWeakPointer<CDRMLease> lease; bool cursorVisible = true; Hyprutils::Math::Vector2D cursorPos; // without hotspot Hyprutils::Math::Vector2D cursorHotspot; bool enabledState = true; // actual enabled state. Should be synced with state->state().enabled after a new frame private: CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, Hyprutils::Memory::CSharedPointer<SDRMConnector> connector_); bool commitState(bool onlyTest = false); Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; Hyprutils::Memory::CSharedPointer<SDRMConnector> connector; Hyprutils::Memory::CSharedPointer<std::function<void(void)>> frameIdle; struct { Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain; Hyprutils::Memory::CSharedPointer<CSwapchain> cursorSwapchain; } mgpu; bool lastCommitNoBuffer = true; friend struct SDRMConnector; friend class CDRMLease; }; struct SDRMPageFlip { Hyprutils::Memory::CWeakPointer<SDRMConnector> connector; }; struct SDRMConnectorCommitData { Hyprutils::Memory::CSharedPointer<CDRMFB> mainFB, cursorFB; bool modeset = false; bool blocking = false; uint32_t flags = 0; bool test = false; drmModeModeInfo modeInfo; struct { uint32_t gammaLut = 0; uint32_t fbDamage = 0; uint32_t modeBlob = 0; bool blobbed = false; bool gammad = false; } atomic; void calculateMode(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector); }; struct SDRMConnector { ~SDRMConnector(); bool init(drmModeConnector* connector); void connect(drmModeConnector* connector); void disconnect(); Hyprutils::Memory::CSharedPointer<SDRMCRTC> getCurrentCRTC(const drmModeConnector* connector); drmModeModeInfo* getCurrentMode(); void parseEDID(std::vector<uint8_t> data); bool commitState(SDRMConnectorCommitData& data); void applyCommit(const SDRMConnectorCommitData& data); void rollbackCommit(const SDRMConnectorCommitData& data); void onPresent(); void recheckCRTCProps(); Hyprutils::Memory::CSharedPointer<CDRMOutput> output; Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; Hyprutils::Memory::CWeakPointer<SDRMConnector> self; std::string szName; drmModeConnection status = DRM_MODE_DISCONNECTED; uint32_t id = 0; std::array<uint64_t, 2> maxBpcBounds = {0, 0}; Hyprutils::Memory::CSharedPointer<SDRMCRTC> crtc; int32_t refresh = 0; uint32_t possibleCrtcs = 0; std::string make, serial, model; bool canDoVrr = false; bool cursorEnabled = false; Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot; Hyprutils::Memory::CSharedPointer<CDRMFB> pendingCursorFB; bool isPageFlipPending = false; SDRMPageFlip pendingPageFlip; bool frameEventScheduled = false; // the current state is invalid and won't commit, don't try to modeset. bool commitTainted = false; Hyprutils::Memory::CSharedPointer<SOutputMode> fallbackMode; struct { bool vrrEnabled = false; } atomic; union UDRMConnectorProps { struct { uint32_t edid; uint32_t dpms; uint32_t link_status; // not guaranteed to exist uint32_t path; uint32_t vrr_capable; // not guaranteed to exist uint32_t subconnector; // not guaranteed to exist uint32_t non_desktop; uint32_t panel_orientation; // not guaranteed to exist uint32_t content_type; // not guaranteed to exist uint32_t max_bpc; // not guaranteed to exist // atomic-modesetting only uint32_t crtc_id; }; uint32_t props[4] = {0}; }; UDRMConnectorProps props; }; class IDRMImplementation { public: virtual bool commit(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) = 0; virtual bool reset() = 0; // moving a cursor IIRC is almost instant on most hardware so we don't have to wait for a commit. virtual bool moveCursor(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, bool skipShedule = false) = 0; }; class CDRMBackend : public IBackendImplementation { public: virtual ~CDRMBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual std::vector<SDRMFormat> getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer<IAllocator> preferredAllocator(); virtual std::vector<SDRMFormat> getRenderableFormats(); Hyprutils::Memory::CWeakPointer<CDRMBackend> self; void log(eBackendLogLevel, const std::string&); bool sessionActive(); int getNonMasterFD(); std::vector<FIdleCallback> idleCallbacks; std::string gpuName; private: CDRMBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend); static std::vector<Hyprutils::Memory::CSharedPointer<CDRMBackend>> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend); bool registerGPU(Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu_, Hyprutils::Memory::CSharedPointer<CDRMBackend> primary_ = {}); bool checkFeatures(); bool initResources(); bool initMgpu(); bool grabFormats(); bool shouldBlit(); void scanConnectors(); void scanLeases(); void restoreAfterVT(); void recheckCRTCs(); void buildGlFormats(const std::vector<SGLFormat>& fmts); Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu; Hyprutils::Memory::CSharedPointer<IDRMImplementation> impl; Hyprutils::Memory::CWeakPointer<CDRMBackend> primary; struct { Hyprutils::Memory::CSharedPointer<IAllocator> allocator; Hyprutils::Memory::CSharedPointer<CDRMRenderer> renderer; // may be null if creation fails } rendererState; Hyprutils::Memory::CWeakPointer<CBackend> backend; std::vector<Hyprutils::Memory::CSharedPointer<SDRMCRTC>> crtcs; std::vector<Hyprutils::Memory::CSharedPointer<SDRMPlane>> planes; std::vector<Hyprutils::Memory::CSharedPointer<SDRMConnector>> connectors; std::vector<SDRMFormat> formats; std::vector<SDRMFormat> glFormats; bool atomic = false; struct { Hyprutils::Math::Vector2D cursorSize; bool supportsAsyncCommit = false; bool supportsAddFb2Modifiers = false; bool supportsTimelines = false; } drmProps; struct { Hyprutils::Signal::CHyprSignalListener sessionActivate; Hyprutils::Signal::CHyprSignalListener gpuChange; Hyprutils::Signal::CHyprSignalListener gpuRemove; } listeners; friend class CBackend; friend class CDRMFB; friend class CDRMFBAttachment; friend struct SDRMConnector; friend struct SDRMCRTC; friend struct SDRMPlane; friend class CDRMOutput; friend struct SDRMPageFlip; friend class CDRMLegacyImpl; friend class CDRMAtomicImpl; friend class CDRMAtomicRequest; friend class CDRMLease; friend class CGBMBuffer; }; }; 0707010000001A000081A400000000000000000000000166C38D3F00000DFC000000000000000000000000000000000000003900000000aquamarine-0.3.3/include/aquamarine/backend/Headless.hpp#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include <hyprutils/memory/WeakPtr.hpp> namespace Aquamarine { class CBackend; class CHeadlessBackend; class CHeadlessOutput : public IOutput { public: virtual ~CHeadlessOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend(); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual bool destroy(); virtual std::vector<SDRMFormat> getRenderFormats(); Hyprutils::Memory::CWeakPointer<CHeadlessOutput> self; private: CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CHeadlessBackend> backend_); Hyprutils::Memory::CWeakPointer<CHeadlessBackend> backend; Hyprutils::Memory::CSharedPointer<std::function<void()>> framecb; bool frameScheduled = false; friend class CHeadlessBackend; }; class CHeadlessBackend : public IBackendImplementation { public: virtual ~CHeadlessBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual std::vector<SDRMFormat> getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer<IAllocator> preferredAllocator(); Hyprutils::Memory::CWeakPointer<CHeadlessBackend> self; private: CHeadlessBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend_); Hyprutils::Memory::CWeakPointer<CBackend> backend; std::vector<Hyprutils::Memory::CSharedPointer<CHeadlessOutput>> outputs; size_t outputIDCounter = 0; class CTimer { public: std::chrono::steady_clock::time_point when; std::function<void(void)> what; bool expired(); }; struct { int timerfd = -1; std::vector<CTimer> timers; } timers; void dispatchTimers(); void updateTimerFD(); friend class CBackend; friend class CHeadlessOutput; }; }; 0707010000001B000081A400000000000000000000000166C38D3F0000015F000000000000000000000000000000000000003500000000aquamarine-0.3.3/include/aquamarine/backend/Misc.hpp#pragma once #include <cstdint> #include <vector> namespace Aquamarine { struct SGLFormat { uint32_t drmFormat = 0; uint64_t modifier = 0; bool external = false; }; struct SDRMFormat { uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */ std::vector<uint64_t> modifiers; }; }; 0707010000001C000081A400000000000000000000000166C38D3F0000228F000000000000000000000000000000000000003800000000aquamarine-0.3.3/include/aquamarine/backend/Session.hpp#pragma once #include <sys/types.h> #include <hyprutils/signal/Signal.hpp> #include <hyprutils/memory/SharedPtr.hpp> #include "../input/Input.hpp" #include <vector> struct udev; struct udev_monitor; struct udev_device; struct libseat; struct libinput; struct libinput_event; struct libinput_device; struct libinput_tablet_tool; namespace Aquamarine { class CBackend; class CSession; class CLibinputDevice; struct SPollFD; class CSessionDevice { public: CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_); ~CSessionDevice(); static Hyprutils::Memory::CSharedPointer<CSessionDevice> openIfKMS(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_); bool supportsKMS(); int fd = -1; int deviceID = -1; dev_t dev; std::string path; enum eChangeEventType : uint32_t { AQ_SESSION_EVENT_CHANGE_HOTPLUG = 0, AQ_SESSION_EVENT_CHANGE_LEASE, }; struct SChangeEvent { eChangeEventType type = AQ_SESSION_EVENT_CHANGE_HOTPLUG; struct { uint32_t connectorID = 0, propID = 0; } hotplug; }; struct { Hyprutils::Signal::CSignal change; Hyprutils::Signal::CSignal remove; } events; private: Hyprutils::Memory::CWeakPointer<CSession> session; }; class CLibinputKeyboard : public IKeyboard { public: CLibinputKeyboard(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputKeyboard() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); virtual void updateLEDs(uint32_t leds); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; friend class CLibinputDevice; }; class CLibinputMouse : public IPointer { public: CLibinputMouse(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputMouse() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; friend class CLibinputDevice; }; class CLibinputTouch : public ITouch { public: CLibinputTouch(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputTouch() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; friend class CLibinputDevice; }; class CLibinputSwitch : public ISwitch { public: CLibinputSwitch(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputSwitch() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); eSwitchType type = AQ_SWITCH_TYPE_UNKNOWN; bool state = false; private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; friend class CLibinputDevice; }; class CLibinputTablet : public ITablet { public: CLibinputTablet(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputTablet() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; friend class CLibinputDevice; }; class CLibinputTabletTool : public ITabletTool { public: CLibinputTabletTool(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev, libinput_tablet_tool* tool); virtual ~CLibinputTabletTool(); virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; libinput_tablet_tool* libinputTool = nullptr; friend class CLibinputDevice; }; class CLibinputTabletPad : public ITabletPad { public: CLibinputTabletPad(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev); virtual ~CLibinputTabletPad(); virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer<CLibinputDevice> device; Hyprutils::Memory::CSharedPointer<ITabletPad::STabletPadGroup> createGroupFromID(int id); friend class CLibinputDevice; }; class CLibinputDevice { public: CLibinputDevice(libinput_device* device, Hyprutils::Memory::CWeakPointer<CSession> session_); ~CLibinputDevice(); void init(); libinput_device* device; Hyprutils::Memory::CWeakPointer<CLibinputDevice> self; Hyprutils::Memory::CWeakPointer<CSession> session; std::string name; Hyprutils::Memory::CSharedPointer<CLibinputKeyboard> keyboard; Hyprutils::Memory::CSharedPointer<CLibinputMouse> mouse; Hyprutils::Memory::CSharedPointer<CLibinputTouch> touch; Hyprutils::Memory::CSharedPointer<CLibinputSwitch> switchy; // :) Hyprutils::Memory::CSharedPointer<CLibinputTablet> tablet; Hyprutils::Memory::CSharedPointer<CLibinputTabletPad> tabletPad; std::vector<Hyprutils::Memory::CSharedPointer<CLibinputTabletTool>> tabletTools; Hyprutils::Memory::CSharedPointer<CLibinputTabletTool> toolFrom(libinput_tablet_tool* tool); }; class CSession { public: ~CSession(); static Hyprutils::Memory::CSharedPointer<CSession> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_); bool active = true; // whether the current vt is ours uint32_t vt = 0; // 0 means unsupported std::string seatName; Hyprutils::Memory::CWeakPointer<CSession> self; std::vector<Hyprutils::Memory::CSharedPointer<CSessionDevice>> sessionDevices; std::vector<Hyprutils::Memory::CSharedPointer<CLibinputDevice>> libinputDevices; udev* udevHandle = nullptr; udev_monitor* udevMonitor = nullptr; libseat* libseatHandle = nullptr; libinput* libinputHandle = nullptr; std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> pollFDs(); void dispatchPendingEventsAsync(); bool switchVT(uint32_t vt); void onReady(); struct SAddDrmCardEvent { std::string path; }; struct { Hyprutils::Signal::CSignal changeActive; Hyprutils::Signal::CSignal addDrmCard; Hyprutils::Signal::CSignal destroy; } events; private: Hyprutils::Memory::CWeakPointer<CBackend> backend; std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> polls; void dispatchUdevEvents(); void dispatchLibinputEvents(); void dispatchLibseatEvents(); void handleLibinputEvent(libinput_event* e); friend class CSessionDevice; friend class CLibinputDevice; }; }; 0707010000001D000081A400000000000000000000000166C38D3F000020C2000000000000000000000000000000000000003800000000aquamarine-0.3.3/include/aquamarine/backend/Wayland.hpp#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" #include <hyprutils/memory/WeakPtr.hpp> #include <wayland-client.h> #include <wayland.hpp> #include <xdg-shell.hpp> #include <linux-dmabuf-v1.hpp> #include <tuple> namespace Aquamarine { class CBackend; class CWaylandBackend; class CWaylandOutput; class CWaylandPointer; typedef std::function<void(void)> FIdleCallback; class CWaylandBuffer { public: CWaylandBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_); ~CWaylandBuffer(); bool good(); bool pendingRelease = false; private: struct { Hyprutils::Memory::CSharedPointer<CCWlBuffer> buffer; } waylandState; Hyprutils::Memory::CWeakPointer<IBuffer> buffer; Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend; friend class CWaylandOutput; }; class CWaylandOutput : public IOutput { public: virtual ~CWaylandOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipShedule = false); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual Hyprutils::Math::Vector2D cursorPlaneSize(); virtual bool destroy(); virtual std::vector<SDRMFormat> getRenderFormats(); Hyprutils::Memory::CWeakPointer<CWaylandOutput> self; private: CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_); Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend; Hyprutils::Memory::CSharedPointer<CWaylandBuffer> wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer); void sendFrameAndSetCallback(); void onFrameDone(); void onEnter(Hyprutils::Memory::CSharedPointer<CCWlPointer> pointer, uint32_t serial); // frame loop bool frameScheduledWhileWaiting = false; bool readyForFrameCallback = false; // true after attaching a buffer bool frameScheduled = false; struct { std::vector<std::pair<Hyprutils::Memory::CWeakPointer<IBuffer>, Hyprutils::Memory::CSharedPointer<CWaylandBuffer>>> buffers; } backendState; struct { Hyprutils::Memory::CSharedPointer<IBuffer> cursorBuffer; Hyprutils::Memory::CSharedPointer<CCWlSurface> cursorSurface; Hyprutils::Memory::CSharedPointer<CCWlBuffer> cursorWlBuffer; uint32_t serial = 0; Hyprutils::Math::Vector2D hotspot; } cursorState; struct { Hyprutils::Memory::CSharedPointer<CCWlSurface> surface; Hyprutils::Memory::CSharedPointer<CCXdgSurface> xdgSurface; Hyprutils::Memory::CSharedPointer<CCXdgToplevel> xdgToplevel; Hyprutils::Memory::CSharedPointer<CCWlCallback> frameCallback; } waylandState; friend class CWaylandBackend; friend class CWaylandPointer; }; class CWaylandKeyboard : public IKeyboard { public: CWaylandKeyboard(Hyprutils::Memory::CSharedPointer<CCWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_); virtual ~CWaylandKeyboard(); virtual const std::string& getName(); Hyprutils::Memory::CSharedPointer<CCWlKeyboard> keyboard; Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend; private: const std::string name = "wl_keyboard"; }; class CWaylandPointer : public IPointer { public: CWaylandPointer(Hyprutils::Memory::CSharedPointer<CCWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_); virtual ~CWaylandPointer(); virtual const std::string& getName(); Hyprutils::Memory::CSharedPointer<CCWlPointer> pointer; Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend; private: const std::string name = "wl_pointer"; }; class CWaylandBackend : public IBackendImplementation { public: virtual ~CWaylandBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual std::vector<SDRMFormat> getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer<IAllocator> preferredAllocator(); Hyprutils::Memory::CWeakPointer<CWaylandBackend> self; private: CWaylandBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend); void initSeat(); void initShell(); bool initDmabuf(); // Hyprutils::Memory::CWeakPointer<CBackend> backend; std::vector<Hyprutils::Memory::CSharedPointer<CWaylandOutput>> outputs; std::vector<Hyprutils::Memory::CSharedPointer<CWaylandKeyboard>> keyboards; std::vector<Hyprutils::Memory::CSharedPointer<CWaylandPointer>> pointers; std::vector<FIdleCallback> idleCallbacks; // pointer focus Hyprutils::Memory::CWeakPointer<CWaylandOutput> focusedOutput; uint32_t lastEnterSerial = 0; // state size_t lastOutputID = 0; // dmabuf formats std::vector<SDRMFormat> dmabufFormats; struct { wl_display* display = nullptr; // hw-s types Hyprutils::Memory::CSharedPointer<CCWlRegistry> registry; Hyprutils::Memory::CSharedPointer<CCWlSeat> seat; Hyprutils::Memory::CSharedPointer<CCWlShm> shm; Hyprutils::Memory::CSharedPointer<CCXdgWmBase> xdg; Hyprutils::Memory::CSharedPointer<CCWlCompositor> compositor; Hyprutils::Memory::CSharedPointer<CCZwpLinuxDmabufV1> dmabuf; Hyprutils::Memory::CSharedPointer<CCZwpLinuxDmabufFeedbackV1> dmabufFeedback; // control bool dmabufFailed = false; } waylandState; struct { int fd = -1; std::string nodeName = ""; } drmState; friend class CBackend; friend class CWaylandKeyboard; friend class CWaylandPointer; friend class CWaylandOutput; friend class CWaylandBuffer; }; }; 0707010000001E000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000003000000000aquamarine-0.3.3/include/aquamarine/backend/drm0707010000001F000081A400000000000000000000000166C38D3F000007AF000000000000000000000000000000000000003B00000000aquamarine-0.3.3/include/aquamarine/backend/drm/Atomic.hpp#pragma once #include "../DRM.hpp" namespace Aquamarine { class CDRMAtomicImpl : public IDRMImplementation { public: CDRMAtomicImpl(Hyprutils::Memory::CSharedPointer<CDRMBackend> backend_); virtual bool commit(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); virtual bool reset(); virtual bool moveCursor(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, bool skipShedule = false); private: bool prepareConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; friend class CDRMAtomicRequest; }; class CDRMAtomicRequest { public: CDRMAtomicRequest(Hyprutils::Memory::CWeakPointer<CDRMBackend> backend); ~CDRMAtomicRequest(); void addConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); bool commit(uint32_t flagssss); void add(uint32_t id, uint32_t prop, uint64_t val); void planeProps(Hyprutils::Memory::CSharedPointer<SDRMPlane> plane, Hyprutils::Memory::CSharedPointer<CDRMFB> fb, uint32_t crtc, Hyprutils::Math::Vector2D pos); void rollback(SDRMConnectorCommitData& data); void apply(SDRMConnectorCommitData& data); bool failed = false; private: void destroyBlob(uint32_t id); void commitBlob(uint32_t* current, uint32_t next); void rollbackBlob(uint32_t* current, uint32_t next); Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; drmModeAtomicReq* req = nullptr; Hyprutils::Memory::CSharedPointer<SDRMConnector> conn; }; }; 07070100000020000081A400000000000000000000000166C38D3F00000376000000000000000000000000000000000000003B00000000aquamarine-0.3.3/include/aquamarine/backend/drm/Legacy.hpp#pragma once #include "../DRM.hpp" namespace Aquamarine { class CDRMLegacyImpl : public IDRMImplementation { public: CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer<CDRMBackend> backend_); virtual bool commit(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); virtual bool reset(); virtual bool moveCursor(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, bool skipShedule = false); private: bool commitInternal(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); bool testInternal(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data); Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; }; }; 07070100000021000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002B00000000aquamarine-0.3.3/include/aquamarine/buffer07070100000022000081A400000000000000000000000166C38D3F00000B51000000000000000000000000000000000000003600000000aquamarine-0.3.3/include/aquamarine/buffer/Buffer.hpp#pragma once #include <array> #include <tuple> #include <hyprutils/signal/Signal.hpp> #include <hyprutils/math/Region.hpp> #include "../misc/Attachment.hpp" namespace Aquamarine { enum eBufferCapability : uint32_t { BUFFER_CAPABILITY_NONE = 0, BUFFER_CAPABILITY_DATAPTR = (1 << 0), }; enum eBufferType : uint32_t { BUFFER_TYPE_DMABUF = 0, BUFFER_TYPE_SHM, BUFFER_TYPE_MISC, }; class CWLBufferResource; struct SDMABUFAttrs { bool success = false; Hyprutils::Math::Vector2D size; uint32_t format = 0; // fourcc uint64_t modifier = 0; int planes = 1; std::array<uint32_t, 4> offsets = {0}; std::array<uint32_t, 4> strides = {0}; std::array<int, 4> fds = {-1, -1, -1, -1}; }; struct SSHMAttrs { bool success = false; int fd = 0; uint32_t format = 0; Hyprutils::Math::Vector2D size; int stride = 0; int64_t offset = 0; }; class IBuffer { public: virtual ~IBuffer() { attachments.clear(); }; virtual eBufferCapability caps() = 0; virtual eBufferType type() = 0; virtual void update(const Hyprutils::Math::CRegion& damage) = 0; virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu virtual bool good() = 0; virtual SDMABUFAttrs dmabuf(); virtual SSHMAttrs shm(); virtual std::tuple<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags); virtual void endDataPtr(); virtual void sendRelease(); virtual void lock(); virtual void unlock(); virtual bool locked(); Hyprutils::Math::Vector2D size; bool opaque = false; bool lockedByBackend = false; CAttachmentManager attachments; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal backendRelease; } events; private: int locks = 0; }; }; 07070100000023000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002A00000000aquamarine-0.3.3/include/aquamarine/input07070100000024000081A400000000000000000000000166C38D3F00002FD6000000000000000000000000000000000000003400000000aquamarine-0.3.3/include/aquamarine/input/Input.hpp#pragma once #include <hyprutils/signal/Signal.hpp> #include <hyprutils/math/Vector2D.hpp> struct libinput_device; namespace Aquamarine { class ITabletTool; class IKeyboard { public: virtual ~IKeyboard() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; virtual void updateLEDs(uint32_t leds); struct SKeyEvent { uint32_t timeMs = 0; uint32_t key = 0; bool pressed = false; }; struct SModifiersEvent { uint32_t depressed = 0, latched = 0, locked = 0, group = 0; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal key; Hyprutils::Signal::CSignal modifiers; } events; }; class IPointer { public: virtual ~IPointer() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum ePointerAxis : uint32_t { AQ_POINTER_AXIS_VERTICAL = 0, AQ_POINTER_AXIS_HORIZONTAL, }; enum ePointerAxisSource : uint32_t { AQ_POINTER_AXIS_SOURCE_WHEEL = 0, AQ_POINTER_AXIS_SOURCE_FINGER, AQ_POINTER_AXIS_SOURCE_CONTINUOUS, AQ_POINTER_AXIS_SOURCE_TILT, }; enum ePointerAxisRelativeDirection : uint32_t { AQ_POINTER_AXIS_RELATIVE_IDENTICAL = 0, AQ_POINTER_AXIS_RELATIVE_INVERTED, }; struct SMoveEvent { uint32_t timeMs = 0; Hyprutils::Math::Vector2D delta, unaccel; }; struct SWarpEvent { uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; }; struct SButtonEvent { uint32_t timeMs = 0; uint32_t button = 0; bool pressed = false; }; struct SAxisEvent { uint32_t timeMs = 0; ePointerAxis axis = AQ_POINTER_AXIS_VERTICAL; ePointerAxisSource source = AQ_POINTER_AXIS_SOURCE_WHEEL; ePointerAxisRelativeDirection direction = AQ_POINTER_AXIS_RELATIVE_IDENTICAL; double delta = 0.0, discrete = 0.0; }; struct SSwipeBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SSwipeUpdateEvent { uint32_t timeMs = 0; uint32_t fingers = 0; Hyprutils::Math::Vector2D delta; }; struct SSwipeEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct SPinchBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SPinchUpdateEvent { uint32_t timeMs = 0; uint32_t fingers = 0; Hyprutils::Math::Vector2D delta; double scale = 1.0, rotation = 0.0; }; struct SPinchEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct SHoldBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SHoldEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal move; Hyprutils::Signal::CSignal warp; Hyprutils::Signal::CSignal button; Hyprutils::Signal::CSignal axis; Hyprutils::Signal::CSignal frame; Hyprutils::Signal::CSignal swipeBegin; Hyprutils::Signal::CSignal swipeUpdate; Hyprutils::Signal::CSignal swipeEnd; Hyprutils::Signal::CSignal pinchBegin; Hyprutils::Signal::CSignal pinchUpdate; Hyprutils::Signal::CSignal pinchEnd; Hyprutils::Signal::CSignal holdBegin; Hyprutils::Signal::CSignal holdEnd; } events; }; class ITouch { public: virtual ~ITouch() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; Hyprutils::Math::Vector2D physicalSize; // in mm, 0,0 if unknown struct SDownEvent { uint32_t timeMs = 0; int32_t touchID = 0; Hyprutils::Math::Vector2D pos; }; struct SUpEvent { uint32_t timeMs = 0; int32_t touchID = 0; }; struct SMotionEvent { uint32_t timeMs = 0; int32_t touchID = 0; Hyprutils::Math::Vector2D pos; }; struct SCancelEvent { uint32_t timeMs = 0; int32_t touchID = 0; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal move; Hyprutils::Signal::CSignal down; Hyprutils::Signal::CSignal up; Hyprutils::Signal::CSignal cancel; Hyprutils::Signal::CSignal frame; } events; }; class ISwitch { public: virtual ~ISwitch() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum eSwitchType : uint32_t { AQ_SWITCH_TYPE_UNKNOWN = 0, AQ_SWITCH_TYPE_LID, AQ_SWITCH_TYPE_TABLET_MODE, }; struct SFireEvent { uint32_t timeMs = 0; eSwitchType type = AQ_SWITCH_TYPE_UNKNOWN; bool enable = false; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal fire; } events; }; enum eTabletToolAxes : uint32_t { AQ_TABLET_TOOL_AXIS_X = (1 << 0), AQ_TABLET_TOOL_AXIS_Y = (1 << 1), AQ_TABLET_TOOL_AXIS_DISTANCE = (1 << 2), AQ_TABLET_TOOL_AXIS_PRESSURE = (1 << 3), AQ_TABLET_TOOL_AXIS_TILT_X = (1 << 4), AQ_TABLET_TOOL_AXIS_TILT_Y = (1 << 5), AQ_TABLET_TOOL_AXIS_ROTATION = (1 << 6), AQ_TABLET_TOOL_AXIS_SLIDER = (1 << 7), AQ_TABLET_TOOL_AXIS_WHEEL = (1 << 8), }; class ITablet { public: virtual ~ITablet() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; uint16_t usbVendorID = 0, usbProductID = 0; Hyprutils::Math::Vector2D physicalSize; // mm std::vector<std::string> paths; struct SAxisEvent { Hyprutils::Memory::CSharedPointer<ITabletTool> tool; uint32_t timeMs = 0, updatedAxes = 0; Hyprutils::Math::Vector2D absolute; Hyprutils::Math::Vector2D delta; Hyprutils::Math::Vector2D tilt; double pressure = 0.0, distance = 0.0, rotation = 0.0, slider = 0.0, wheelDelta = 0.0; }; struct SProximityEvent { Hyprutils::Memory::CSharedPointer<ITabletTool> tool; uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; bool in = false; }; struct STipEvent { Hyprutils::Memory::CSharedPointer<ITabletTool> tool; uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; bool down = false; }; struct SButtonEvent { Hyprutils::Memory::CSharedPointer<ITabletTool> tool; uint32_t timeMs = 0, button = 0; bool down = false; }; struct { Hyprutils::Signal::CSignal axis; Hyprutils::Signal::CSignal proximity; Hyprutils::Signal::CSignal tip; Hyprutils::Signal::CSignal button; Hyprutils::Signal::CSignal destroy; } events; }; class ITabletTool { public: virtual ~ITabletTool() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum eTabletToolType : uint32_t { AQ_TABLET_TOOL_TYPE_INVALID = 0, AQ_TABLET_TOOL_TYPE_PEN, AQ_TABLET_TOOL_TYPE_ERASER, AQ_TABLET_TOOL_TYPE_BRUSH, AQ_TABLET_TOOL_TYPE_PENCIL, AQ_TABLET_TOOL_TYPE_AIRBRUSH, AQ_TABLET_TOOL_TYPE_MOUSE, AQ_TABLET_TOOL_TYPE_LENS, AQ_TABLET_TOOL_TYPE_TOTEM, }; eTabletToolType type = AQ_TABLET_TOOL_TYPE_INVALID; uint64_t serial = 0, id = 0; enum eTabletToolCapabilities : uint32_t { AQ_TABLET_TOOL_CAPABILITY_TILT = (1 << 0), AQ_TABLET_TOOL_CAPABILITY_PRESSURE = (1 << 1), AQ_TABLET_TOOL_CAPABILITY_DISTANCE = (1 << 2), AQ_TABLET_TOOL_CAPABILITY_ROTATION = (1 << 3), AQ_TABLET_TOOL_CAPABILITY_SLIDER = (1 << 4), AQ_TABLET_TOOL_CAPABILITY_WHEEL = (1 << 5), }; uint32_t capabilities = 0; // enum eTabletToolCapabilities struct { Hyprutils::Signal::CSignal destroy; } events; }; class ITabletPad { public: virtual ~ITabletPad() { events.destroy.emit(); } struct STabletPadGroup { std::vector<uint32_t> buttons, strips, rings; uint16_t modes = 0; }; virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; uint16_t buttons = 0, rings = 0, strips = 0; std::vector<std::string> paths; std::vector<Hyprutils::Memory::CSharedPointer<STabletPadGroup>> groups; // struct SButtonEvent { uint32_t timeMs = 0, button = 0; bool down = false; uint16_t mode = 0, group = 0; }; enum eTabletPadRingSource : uint16_t { AQ_TABLET_PAD_RING_SOURCE_UNKNOWN = 0, AQ_TABLET_PAD_RING_SOURCE_FINGER, }; enum eTabletPadStripSource : uint16_t { AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN = 0, AQ_TABLET_PAD_STRIP_SOURCE_FINGER, }; struct SRingEvent { uint32_t timeMs = 0; eTabletPadRingSource source = AQ_TABLET_PAD_RING_SOURCE_UNKNOWN; uint16_t ring = 0; double pos = 0.0; uint16_t mode = 0; }; struct SStripEvent { uint32_t timeMs = 0; eTabletPadStripSource source = AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN; uint16_t strip = 0; double pos = 0.0; uint16_t mode = 0; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal button; Hyprutils::Signal::CSignal ring; Hyprutils::Signal::CSignal strip; Hyprutils::Signal::CSignal attach; } events; }; }07070100000025000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002900000000aquamarine-0.3.3/include/aquamarine/misc07070100000026000081A400000000000000000000000166C38D3F00000467000000000000000000000000000000000000003800000000aquamarine-0.3.3/include/aquamarine/misc/Attachment.hpp#pragma once #include <vector> #include <hyprutils/memory/SharedPtr.hpp> namespace Aquamarine { enum eAttachmentType : uint32_t { AQ_ATTACHMENT_DRM_BUFFER = 0, AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE, AQ_ATTACHMENT_DRM_RENDERER_DATA, }; class IAttachment { public: virtual ~IAttachment() { ; } virtual eAttachmentType type() = 0; }; class CAttachmentManager { public: bool has(eAttachmentType type); Hyprutils::Memory::CSharedPointer<IAttachment> get(eAttachmentType type); void add(Hyprutils::Memory::CSharedPointer<IAttachment> attachment); void remove(Hyprutils::Memory::CSharedPointer<IAttachment> attachment); void removeByType(eAttachmentType type); void clear(); private: std::vector<Hyprutils::Memory::CSharedPointer<IAttachment>> attachments; }; }; 07070100000027000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002B00000000aquamarine-0.3.3/include/aquamarine/output07070100000028000081A400000000000000000000000166C38D3F000020B8000000000000000000000000000000000000003600000000aquamarine-0.3.3/include/aquamarine/output/Output.hpp#pragma once #include <vector> #include <optional> #include <hyprutils/signal/Signal.hpp> #include <hyprutils/memory/SharedPtr.hpp> #include <hyprutils/math/Region.hpp> #include <drm_fourcc.h> #include <xf86drmMode.h> #include "../allocator/Swapchain.hpp" #include "../buffer/Buffer.hpp" #include "../backend/Misc.hpp" namespace Aquamarine { class IBackendImplementation; struct SOutputMode { Hyprutils::Math::Vector2D pixelSize; unsigned int refreshRate = 0 /* in mHz */; bool preferred = false; std::optional<drmModeModeInfo> modeInfo; // if this is a drm mode, this will be populated. }; enum eOutputPresentationMode : uint32_t { AQ_OUTPUT_PRESENTATION_VSYNC = 0, AQ_OUTPUT_PRESENTATION_IMMEDIATE, // likely tearing }; enum eSubpixelMode : uint32_t { AQ_SUBPIXEL_UNKNOWN = 0, AQ_SUBPIXEL_NONE, AQ_SUBPIXEL_HORIZONTAL_RGB, AQ_SUBPIXEL_HORIZONTAL_BGR, AQ_SUBPIXEL_VERTICAL_RGB, AQ_SUBPIXEL_VERTICAL_BGR, }; class IOutput; class COutputState { public: enum eOutputStateProperties : uint32_t { AQ_OUTPUT_STATE_DAMAGE = (1 << 0), AQ_OUTPUT_STATE_ENABLED = (1 << 1), AQ_OUTPUT_STATE_ADAPTIVE_SYNC = (1 << 2), AQ_OUTPUT_STATE_PRESENTATION_MODE = (1 << 3), AQ_OUTPUT_STATE_GAMMA_LUT = (1 << 4), AQ_OUTPUT_STATE_MODE = (1 << 5), AQ_OUTPUT_STATE_FORMAT = (1 << 6), AQ_OUTPUT_STATE_BUFFER = (1 << 7), AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE = (1 << 8), AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE = (1 << 9), }; struct SInternalState { uint32_t committed = 0; // enum eOutputStateProperties Hyprutils::Math::CRegion damage; bool enabled = false; bool adaptiveSync = false; eOutputPresentationMode presentationMode = AQ_OUTPUT_PRESENTATION_VSYNC; std::vector<uint16_t> gammaLut; // Gamma lut in the format [r,g,b]+ Hyprutils::Math::Vector2D lastModeSize; Hyprutils::Memory::CWeakPointer<SOutputMode> mode; Hyprutils::Memory::CSharedPointer<SOutputMode> customMode; uint32_t drmFormat = DRM_FORMAT_INVALID; Hyprutils::Memory::CSharedPointer<IBuffer> buffer; int64_t explicitInFence = -1, explicitOutFence = -1; }; const SInternalState& state(); void addDamage(const Hyprutils::Math::CRegion& region); void clearDamage(); void setEnabled(bool enabled); void setAdaptiveSync(bool enabled); void setPresentationMode(eOutputPresentationMode mode); void setGammaLut(const std::vector<uint16_t>& lut); void setMode(Hyprutils::Memory::CSharedPointer<SOutputMode> mode); void setCustomMode(Hyprutils::Memory::CSharedPointer<SOutputMode> mode); void setFormat(uint32_t drmFormat); void setBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer); void setExplicitInFence(int64_t fenceFD); // -1 removes void setExplicitOutFence(int64_t fenceFD); // -1 removes void resetExplicitFences(); private: SInternalState internalState; void onCommit(); // clears a few props like damage and committed. friend class IOutput; friend class CWaylandOutput; friend class CDRMOutput; friend class CHeadlessOutput; }; class IOutput { public: virtual ~IOutput() { ; } enum scheduleFrameReason : uint32_t { AQ_SCHEDULE_UNKNOWN = 0, AQ_SCHEDULE_NEW_CONNECTOR, AQ_SCHEDULE_CURSOR_VISIBLE, AQ_SCHEDULE_CURSOR_SHAPE, AQ_SCHEDULE_CURSOR_MOVE, AQ_SCHEDULE_CLIENT_UNKNOWN, AQ_SCHEDULE_DAMAGE, AQ_SCHEDULE_NEW_MONITOR, AQ_SCHEDULE_RENDER_MONITOR, AQ_SCHEDULE_NEEDS_FRAME, AQ_SCHEDULE_ANIMATION, AQ_SCHEDULE_ANIMATION_DAMAGE, }; virtual bool commit() = 0; virtual bool test() = 0; virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend() = 0; virtual std::vector<SDRMFormat> getRenderFormats() = 0; virtual Hyprutils::Memory::CSharedPointer<SOutputMode> preferredMode(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipShedule = false); // includes the hotspot virtual void setCursorVisible(bool visible); // moving the cursor will make it visible again without this util virtual Hyprutils::Math::Vector2D cursorPlaneSize(); // -1, -1 means no set size, 0, 0 means error virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual size_t getGammaSize(); virtual bool destroy(); // not all backends allow this!!! std::string name, description, make, model, serial; Hyprutils::Math::Vector2D physicalSize; bool enabled = false; bool nonDesktop = false; eSubpixelMode subpixel = AQ_SUBPIXEL_NONE; bool vrrCapable = false, vrrActive = false; bool needsFrame = false; bool supportsExplicit = false; // std::vector<Hyprutils::Memory::CSharedPointer<SOutputMode>> modes; Hyprutils::Memory::CSharedPointer<COutputState> state = Hyprutils::Memory::makeShared<COutputState>(); Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain; // enum eOutputPresentFlags : uint32_t { AQ_OUTPUT_PRESENT_VSYNC = (1 << 0), AQ_OUTPUT_PRESENT_HW_CLOCK = (1 << 1), AQ_OUTPUT_PRESENT_HW_COMPLETION = (1 << 2), AQ_OUTPUT_PRESENT_ZEROCOPY = (1 << 3), }; struct SStateEvent { Hyprutils::Math::Vector2D size; // if {0,0}, means it needs a reconfigure. }; struct SPresentEvent { bool presented = true; timespec* when = nullptr; unsigned int seq = 0; int refresh = 0; uint32_t flags = 0; }; struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal frame; Hyprutils::Signal::CSignal needsFrame; Hyprutils::Signal::CSignal present; Hyprutils::Signal::CSignal commit; Hyprutils::Signal::CSignal state; } events; }; } 07070100000029000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001500000000aquamarine-0.3.3/nix0707010000002A000081A400000000000000000000000166C38D3F000003A4000000000000000000000000000000000000002100000000aquamarine-0.3.3/nix/default.nix{ lib, stdenv, cmake, hwdata, hyprutils, hyprwayland-scanner, libdisplay-info, libdrm, libffi, libGL, libinput, libseat, mesa, pixman, pkg-config, udev, wayland, wayland-protocols, version ? "git", doCheck ? false, }: stdenv.mkDerivation { pname = "aquamarine"; inherit version doCheck; src = ../.; strictDeps = true; nativeBuildInputs = [ cmake hyprwayland-scanner pkg-config ]; buildInputs = [ hwdata hyprutils libdisplay-info libdrm libffi libGL libinput libseat mesa pixman udev wayland wayland-protocols ]; outputs = ["out" "dev"]; cmakeBuildType = "RelWithDebInfo"; dontStrip = true; meta = { homepage = "https://github.com/hyprwm/aquamarine"; description = "A very light linux rendering backend library"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; }; } 0707010000002B000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001B00000000aquamarine-0.3.3/protocols0707010000002C000081A400000000000000000000000166C38D3F00000000000000000000000000000000000000000000002400000000aquamarine-0.3.3/protocols/.gitkeep0707010000002D000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001500000000aquamarine-0.3.3/src0707010000002E000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001F00000000aquamarine-0.3.3/src/allocator0707010000002F000081A400000000000000000000000166C38D3F00002FCF000000000000000000000000000000000000002700000000aquamarine-0.3.3/src/allocator/GBM.cpp#include <aquamarine/allocator/GBM.hpp> #include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/DRM.hpp> #include <aquamarine/allocator/Swapchain.hpp> #include "FormatUtils.hpp" #include "Shared.hpp" #include <xf86drm.h> #include <gbm.h> #include <unistd.h> #include "../backend/drm/Renderer.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer static SDRMFormat guessFormatFrom(std::vector<SDRMFormat> formats, bool cursor) { if (formats.empty()) return SDRMFormat{}; if (!cursor) { /* Try to find 10bpp formats first, as they offer better color precision. For cursors, don't, as these almost never support that. */ if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB2101010; }); it != formats.end()) return *it; if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB2101010; }); it != formats.end()) return *it; } if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB8888; }); it != formats.end()) return *it; if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB8888; }); it != formats.end()) return *it; for (auto& f : formats) { auto name = fourccToName(f.drmFormat); /* 10 bpp RGB */ if (name.contains("30")) return f; } for (auto& f : formats) { auto name = fourccToName(f.drmFormat); /* 8 bpp RGB */ if (name.contains("24")) return f; } return formats.at(0); } Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer<CGBMAllocator> allocator_, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain) : allocator(allocator_) { if (!allocator) return; attrs.size = params.size; attrs.format = params.format; size = attrs.size; const bool CURSOR = params.cursor && params.scanout; const bool MULTIGPU = params.multigpu && params.scanout; TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Allocating a buffer: size {}, format {}, cursor: {}, multigpu: {}, scanout: {}", attrs.size, fourccToName(attrs.format), CURSOR, MULTIGPU, params.scanout))); const auto FORMATS = CURSOR ? swapchain->backendImpl->getCursorFormats() : swapchain->backendImpl->getRenderFormats(); const auto RENDERABLE = swapchain->backendImpl->getRenderableFormats(); std::vector<uint64_t> explicitModifiers; if (attrs.format == DRM_FORMAT_INVALID) { attrs.format = guessFormatFrom(FORMATS, CURSOR).drmFormat; if (attrs.format != DRM_FORMAT_INVALID) allocator->backend->log(AQ_LOG_DEBUG, std::format("GBM: Automatically selected format {} for new GBM buffer", fourccToName(attrs.format))); } if (attrs.format == DRM_FORMAT_INVALID) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: no format found"); return; } // check if we can use modifiers. If the requested support has any explicit modifier // supported by the primary backend, we can. for (auto& f : FORMATS) { if (f.drmFormat != attrs.format) continue; for (auto& m : f.modifiers) { if (m == DRM_FORMAT_MOD_INVALID) continue; if (!RENDERABLE.empty() && params.scanout && !CURSOR && !MULTIGPU) { // regular scanout plane, check if the format is renderable auto rformat = std::find_if(RENDERABLE.begin(), RENDERABLE.end(), [f](const auto& e) { return e.drmFormat == f.drmFormat; }); if (rformat == RENDERABLE.end()) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping format {} as it's not renderable", fourccToName(f.drmFormat)))); break; } if (std::find(rformat->modifiers.begin(), rformat->modifiers.end(), m) == rformat->modifiers.end()) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping modifier 0x{:x} as it's not renderable", m))); continue; } } explicitModifiers.push_back(m); } } // FIXME: Nvidia cannot render to linear buffers. What do? if (MULTIGPU) { allocator->backend->log(AQ_LOG_DEBUG, "GBM: Buffer is marked as multigpu, forcing linear"); explicitModifiers = {DRM_FORMAT_MOD_LINEAR}; } if (explicitModifiers.empty()) { // fall back to using a linear buffer. explicitModifiers.push_back(DRM_FORMAT_MOD_LINEAR); } uint32_t flags = GBM_BO_USE_RENDERING; if (params.scanout) flags |= GBM_BO_USE_SCANOUT; if (explicitModifiers.empty()) { allocator->backend->log(AQ_LOG_WARNING, "GBM: Using modifier-less allocation"); bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags); } else { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Using modifier-based allocation, modifiers: {}", explicitModifiers.size()))); for (auto& mod : explicitModifiers) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: | mod 0x{:x}", mod))); } bo = gbm_bo_create_with_modifiers2(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size(), flags); if (!bo && CURSOR) { // allow non-renderable cursor buffer for nvidia allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers and flags failed, falling back to modifiers without flags"); bo = gbm_bo_create_with_modifiers(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size()); } if (!bo) { if (explicitModifiers.size() == 1 && explicitModifiers[0] == DRM_FORMAT_MOD_LINEAR) { flags |= GBM_BO_USE_LINEAR; allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to modifier-less allocation"); } else allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to implicit"); bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags); } } if (!bo) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: bo null"); return; } attrs.planes = gbm_bo_get_plane_count(bo); attrs.modifier = (flags & GBM_BO_USE_LINEAR) ? DRM_FORMAT_MOD_LINEAR : gbm_bo_get_modifier(bo); for (size_t i = 0; i < (size_t)attrs.planes; ++i) { attrs.strides.at(i) = gbm_bo_get_stride_for_plane(bo, i); attrs.offsets.at(i) = gbm_bo_get_offset(bo, i); attrs.fds.at(i) = gbm_bo_get_fd_for_plane(bo, i); if (attrs.fds.at(i) < 0) { allocator->backend->log(AQ_LOG_ERROR, std::format("GBM: Failed to query fd for plane {}", i)); for (size_t j = 0; j < i; ++j) { close(attrs.fds.at(j)); } attrs.planes = 0; return; } } attrs.success = true; auto modName = drmGetFormatModifierName(attrs.modifier); allocator->backend->log(AQ_LOG_DEBUG, std::format("GBM: Allocated a new buffer with size {} and format {} with modifier {} aka {}", attrs.size, fourccToName(attrs.format), attrs.modifier, modName ? modName : "Unknown")); free(modName); if (params.scanout && swapchain->backendImpl->type() == AQ_BACKEND_DRM) { // clear the buffer using the DRM renderer to avoid uninitialized mem auto impl = (CDRMBackend*)swapchain->backendImpl.get(); if (impl->rendererState.renderer) impl->rendererState.renderer->clearBuffer(this); } } Aquamarine::CGBMBuffer::~CGBMBuffer() { events.destroy.emit(); if (bo) { if (gboMapping) gbm_bo_unmap(bo, gboMapping); // FIXME: is it needed before destroy? gbm_bo_destroy(bo); } for (size_t i = 0; i < (size_t)attrs.planes; i++) close(attrs.fds.at(i)); } eBufferCapability Aquamarine::CGBMBuffer::caps() { return (Aquamarine::eBufferCapability)0; } eBufferType Aquamarine::CGBMBuffer::type() { return Aquamarine::eBufferType::BUFFER_TYPE_DMABUF; } void Aquamarine::CGBMBuffer::update(const Hyprutils::Math::CRegion& damage) { ; } bool Aquamarine::CGBMBuffer::isSynchronous() { return false; } bool Aquamarine::CGBMBuffer::good() { return true; } SDMABUFAttrs Aquamarine::CGBMBuffer::dmabuf() { return attrs; } std::tuple<uint8_t*, uint32_t, size_t> Aquamarine::CGBMBuffer::beginDataPtr(uint32_t flags) { uint32_t stride = 0; if (boBuffer) allocator->backend->log(AQ_LOG_ERROR, "beginDataPtr is called a second time without calling endDataPtr first. Returning old mapping"); else boBuffer = gbm_bo_map(bo, 0, 0, attrs.size.x, attrs.size.y, flags, &stride, &gboMapping); return {(uint8_t*)boBuffer, attrs.format, stride * attrs.size.y}; } void Aquamarine::CGBMBuffer::endDataPtr() { if (gboMapping) { gbm_bo_unmap(bo, gboMapping); gboMapping = nullptr; boBuffer = nullptr; } } CGBMAllocator::~CGBMAllocator() { if (gbmDevice) gbm_device_destroy(gbmDevice); } SP<CGBMAllocator> Aquamarine::CGBMAllocator::create(int drmfd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_) { uint64_t capabilities = 0; if (drmGetCap(drmfd_, DRM_CAP_PRIME, &capabilities) || !(capabilities & DRM_PRIME_CAP_EXPORT)) { backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: PRIME export is not supported by the gpu."); return nullptr; } auto allocator = SP<CGBMAllocator>(new CGBMAllocator(drmfd_, backend_)); if (!allocator->gbmDevice) { backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: gbm failed to create a device."); return nullptr; } backend_->log(AQ_LOG_DEBUG, std::format("Created a GBM allocator with drm fd {}", drmfd_)); allocator->self = allocator; return allocator; } Aquamarine::CGBMAllocator::CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_) : fd(fd_), backend(backend_) { gbmDevice = gbm_create_device(fd_); if (!gbmDevice) { backend->log(AQ_LOG_ERROR, std::format("Couldn't open a GBM device at fd {}", fd_)); return; } gbmDeviceBackendName = gbm_device_get_backend_name(gbmDevice); auto drmName_ = drmGetDeviceNameFromFd2(fd_); drmName = drmName_; free(drmName_); } SP<IBuffer> Aquamarine::CGBMAllocator::acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain_) { if (params.size.x < 1 || params.size.y < 1) { backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with invalid size {}", params.size)); return nullptr; } auto newBuffer = SP<CGBMBuffer>(new CGBMBuffer(params, self, swapchain_)); if (!newBuffer->good()) { backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with size {} and format {}", params.size, fourccToName(params.format))); return nullptr; } buffers.emplace_back(newBuffer); std::erase_if(buffers, [](const auto& b) { return b.expired(); }); return newBuffer; } Hyprutils::Memory::CSharedPointer<CBackend> Aquamarine::CGBMAllocator::getBackend() { return backend.lock(); } int Aquamarine::CGBMAllocator::drmFD() { return fd; } eAllocatorType Aquamarine::CGBMAllocator::type() { return AQ_ALLOCATOR_TYPE_GBM; } 07070100000030000081A400000000000000000000000166C38D3F00001043000000000000000000000000000000000000002D00000000aquamarine-0.3.3/src/allocator/Swapchain.cpp#include <aquamarine/allocator/Swapchain.hpp> #include <aquamarine/backend/Backend.hpp> #include "FormatUtils.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer SP<CSwapchain> Aquamarine::CSwapchain::create(SP<IAllocator> allocator_, SP<IBackendImplementation> backendImpl_) { auto p = SP<CSwapchain>(new CSwapchain(allocator_, backendImpl_)); p->self = p; return p; } Aquamarine::CSwapchain::CSwapchain(SP<IAllocator> allocator_, SP<IBackendImplementation> backendImpl_) : allocator(allocator_), backendImpl(backendImpl_) { if (!allocator || !backendImpl) return; } bool Aquamarine::CSwapchain::reconfigure(const SSwapchainOptions& options_) { if (!allocator) return false; if (options_.size == Vector2D{} || options_.length == 0) { // clear the swapchain allocator->getBackend()->log(AQ_LOG_DEBUG, "Swapchain: Clearing"); buffers.clear(); options = options_; return true; } if ((options_.format == options.format || options_.format == DRM_FORMAT_INVALID) && options_.size == options.size && options_.length == options.length) return true; // no need to reconfigure if ((options_.format == options.format || options_.format == DRM_FORMAT_INVALID) && options_.size == options.size) { bool ok = resize(options_.length); if (!ok) return false; options = options_; allocator->getBackend()->log(AQ_LOG_DEBUG, std::format("Swapchain: Resized a {} {} swapchain to length {}", options.size, fourccToName(options.format), options.length)); return true; } bool ok = fullReconfigure(options_); if (!ok) return false; options = options_; if (options.format == DRM_FORMAT_INVALID) options.format = buffers.at(0)->dmabuf().format; allocator->getBackend()->log(AQ_LOG_DEBUG, std::format("Swapchain: Reconfigured a swapchain to {} {} of length {}", options.size, fourccToName(options.format), options.length)); return true; } SP<IBuffer> Aquamarine::CSwapchain::next(int* age) { if (!allocator || options.length <= 0) return nullptr; lastAcquired = (lastAcquired + 1) % options.length; if (age) *age = 1; return buffers.at(lastAcquired); } bool Aquamarine::CSwapchain::fullReconfigure(const SSwapchainOptions& options_) { buffers.clear(); for (size_t i = 0; i < options_.length; ++i) { auto buf = allocator->acquire( SAllocatorBufferParams{.size = options_.size, .format = options_.format, .scanout = options_.scanout, .cursor = options_.cursor, .multigpu = options_.multigpu}, self.lock()); if (!buf) { allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); return false; } buffers.emplace_back(buf); } return true; } bool Aquamarine::CSwapchain::resize(size_t newSize) { if (newSize == buffers.size()) return true; if (newSize < buffers.size()) { while (buffers.size() > newSize) { buffers.pop_back(); } } else { while (buffers.size() < newSize) { auto buf = allocator->acquire(SAllocatorBufferParams{.size = options.size, .format = options.format, .scanout = options.scanout, .cursor = options.cursor}, self.lock()); if (!buf) { allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); return false; } buffers.emplace_back(buf); } } return true; } bool Aquamarine::CSwapchain::contains(SP<IBuffer> buffer) { return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); } const SSwapchainOptions& Aquamarine::CSwapchain::currentOptions() { return options; } void Aquamarine::CSwapchain::rollback() { lastAcquired--; if (lastAcquired < 0) lastAcquired = options.length - 1; } SP<IAllocator> Aquamarine::CSwapchain::getAllocator() { return allocator; } 07070100000031000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001D00000000aquamarine-0.3.3/src/backend07070100000032000081A400000000000000000000000166C38D3F000028A9000000000000000000000000000000000000002900000000aquamarine-0.3.3/src/backend/Backend.cpp#include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/Wayland.hpp> #include <aquamarine/backend/Headless.hpp> #include <aquamarine/backend/DRM.hpp> #include <aquamarine/allocator/GBM.hpp> #include <sys/poll.h> #include <thread> #include <chrono> #include <sys/timerfd.h> #include <time.h> #include <string.h> #include <xf86drm.h> #include <fcntl.h> #include <unistd.h> using namespace Hyprutils::Memory; using namespace Aquamarine; #define SP CSharedPointer #define TIMESPEC_NSEC_PER_SEC 1000000000L static void timespecAddNs(timespec* pTimespec, int64_t delta) { int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; pTimespec->tv_nsec += (long)delta_ns_low; if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++pTimespec->tv_sec; } } static const char* backendTypeToName(eBackendType type) { switch (type) { case AQ_BACKEND_DRM: return "drm"; case AQ_BACKEND_HEADLESS: return "headless"; case AQ_BACKEND_WAYLAND: return "wayland"; default: break; } return "invalid"; } Aquamarine::CBackend::CBackend() { ; } Aquamarine::SBackendImplementationOptions::SBackendImplementationOptions() { backendType = AQ_BACKEND_WAYLAND; backendRequestMode = AQ_BACKEND_REQUEST_IF_AVAILABLE; } Aquamarine::SBackendOptions::SBackendOptions() { logFunction = nullptr; } Hyprutils::Memory::CSharedPointer<CBackend> Aquamarine::CBackend::create(const std::vector<SBackendImplementationOptions>& backends, const SBackendOptions& options) { auto backend = SP<CBackend>(new CBackend()); backend->options = options; backend->implementationOptions = backends; backend->self = backend; if (backends.size() <= 0) return nullptr; backend->log(AQ_LOG_DEBUG, "Creating an Aquamarine backend!"); for (auto& b : backends) { if (b.backendType == AQ_BACKEND_WAYLAND) { auto ref = SP<CWaylandBackend>(new CWaylandBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; } else if (b.backendType == AQ_BACKEND_DRM) { auto ref = CDRMBackend::attempt(backend); if (ref.empty()) { backend->log(AQ_LOG_ERROR, "DRM Backend failed"); continue; } for (auto& r : ref) { backend->implementations.emplace_back(r); } } else if (b.backendType == AQ_BACKEND_HEADLESS) { auto ref = SP<CHeadlessBackend>(new CHeadlessBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; } else { backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); continue; } } // create a timerfd for idle events backend->idle.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); return backend; } Aquamarine::CBackend::~CBackend() { ; } bool Aquamarine::CBackend::start() { log(AQ_LOG_DEBUG, "Starting the Aquamarine backend!"); bool fallback = false; int started = 0; auto optionsForType = [this](eBackendType type) -> SBackendImplementationOptions { for (auto& o : implementationOptions) { if (o.backendType == type) return o; } return SBackendImplementationOptions{}; }; for (size_t i = 0; i < implementations.size(); ++i) { const bool ok = implementations.at(i)->start(); if (!ok) { log(AQ_LOG_ERROR, std::format("Requested backend ({}) could not start, enabling fallbacks", backendTypeToName(implementations.at(i)->type()))); fallback = true; if (optionsForType(implementations.at(i)->type()).backendRequestMode == AQ_BACKEND_REQUEST_MANDATORY) { log(AQ_LOG_CRITICAL, std::format("Requested backend ({}) could not start and it's mandatory, cannot continue!", backendTypeToName(implementations.at(i)->type()))); implementations.clear(); return false; } } else started++; } if (implementations.empty() || started <= 0) { log(AQ_LOG_CRITICAL, "No backend could be opened. Make sure there was a correct backend passed to CBackend, and that your environment supports at least one of them."); return false; } // erase failed impls std::erase_if(implementations, [this](const auto& i) { bool failed = i->pollFDs().empty(); if (failed) log(AQ_LOG_ERROR, std::format("Implementation {} failed, erasing.", backendTypeToName(i->type()))); return failed; }); // TODO: obviously change this when (if) we add different allocators. for (auto& b : implementations) { if (b->drmFD() >= 0) { auto fd = reopenDRMNode(b->drmFD()); if (fd < 0) { // this is critical, we cannot create an allocator properly log(AQ_LOG_CRITICAL, "Failed to create an allocator (reopenDRMNode failed)"); return false; } primaryAllocator = CGBMAllocator::create(fd, self); break; } } if (!primaryAllocator) return false; ready = true; for (auto& b : implementations) { b->onReady(); } sessionFDs = session ? session->pollFDs() : std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>>{}; return true; } void Aquamarine::CBackend::log(eBackendLogLevel level, const std::string& msg) { if (!options.logFunction) return; options.logFunction(level, msg); } std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> Aquamarine::CBackend::getPollFDs() { std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> result; for (auto& i : implementations) { auto pollfds = i->pollFDs(); for (auto& p : pollfds) { log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for implementation {}", p->fd, backendTypeToName(i->type()))); result.emplace_back(p); } } for (auto& sfd : sessionFDs) { log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for session", sfd->fd)); result.emplace_back(sfd); } log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for idle", idle.fd)); result.emplace_back(makeShared<SPollFD>(idle.fd, [this]() { dispatchIdle(); })); return result; } int Aquamarine::CBackend::drmFD() { for (auto& i : implementations) { int fd = i->drmFD(); if (fd < 0) continue; return fd; } return -1; } bool Aquamarine::CBackend::hasSession() { return session; } std::vector<SDRMFormat> Aquamarine::CBackend::getPrimaryRenderFormats() { for (auto& b : implementations) { if (b->type() != AQ_BACKEND_DRM && b->type() != AQ_BACKEND_WAYLAND) continue; return b->getRenderFormats(); } for (auto& b : implementations) { return b->getRenderFormats(); } return {}; } const std::vector<SP<IBackendImplementation>>& Aquamarine::CBackend::getImplementations() { return implementations; } void Aquamarine::CBackend::addIdleEvent(SP<std::function<void(void)>> fn) { auto r = idle.pending.emplace_back(fn); updateIdleTimer(); } void Aquamarine::CBackend::updateIdleTimer() { uint64_t ADD_NS = idle.pending.empty() ? TIMESPEC_NSEC_PER_SEC * 240ULL /* 240s, 4 mins */ : 0; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecAddNs(&now, ADD_NS); itimerspec ts = {.it_value = now}; if (timerfd_settime(idle.fd, TFD_TIMER_ABSTIME, &ts, nullptr)) log(AQ_LOG_ERROR, std::format("backend: failed to arm timerfd: {}", strerror(errno))); } void Aquamarine::CBackend::removeIdleEvent(SP<std::function<void(void)>> pfn) { std::erase(idle.pending, pfn); } void Aquamarine::CBackend::dispatchIdle() { auto cpy = idle.pending; idle.pending.clear(); for (auto& i : cpy) { if (i && *i) (*i)(); } updateIdleTimer(); } // Yoinked from wlroots, render/allocator/allocator.c // Ref-counting reasons, see https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110 int Aquamarine::CBackend::reopenDRMNode(int drmFD, bool allowRenderNode) { if (drmIsMaster(drmFD)) { // Only recent kernels support empty leases uint32_t lesseeID = 0; int leaseFD = drmModeCreateLease(drmFD, nullptr, 0, O_CLOEXEC, &lesseeID); if (leaseFD >= 0) { return leaseFD; } else if (leaseFD != -EINVAL && leaseFD != -EOPNOTSUPP) { log(AQ_LOG_ERROR, "reopenDRMNode: drmModeCreateLease failed"); return -1; } log(AQ_LOG_DEBUG, "reopenDRMNode: drmModeCreateLease failed, falling back to open"); } char* name = nullptr; if (allowRenderNode) name = drmGetRenderDeviceNameFromFd(drmFD); if (!name) { // primary node or no name name = drmGetDeviceNameFromFd2(drmFD); if (!name) { log(AQ_LOG_ERROR, "reopenDRMNode: drmGetDeviceNameFromFd2 failed"); return -1; } } log(AQ_LOG_DEBUG, std::format("reopenDRMNode: opening node {}", name)); int newFD = open(name, O_RDWR | O_CLOEXEC); if (newFD < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: failed to open node {}", name)); free(name); return -1; } free(name); // We need to authenticate if we are using a DRM primary node and are the master if (drmIsMaster(drmFD) && drmGetNodeTypeFromFd(newFD) == DRM_NODE_PRIMARY) { drm_magic_t magic; if (int ret = drmGetMagic(newFD, &magic); ret < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmGetMagic failed: {}", strerror(-ret))); close(newFD); return -1; } if (int ret = drmAuthMagic(drmFD, magic); ret < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmAuthMagic failed: {}", strerror(-ret))); close(newFD); return -1; } } return newFD; } std::vector<SDRMFormat> Aquamarine::IBackendImplementation::getRenderableFormats() { return {}; } 07070100000033000081A400000000000000000000000166C38D3F0000160C000000000000000000000000000000000000002A00000000aquamarine-0.3.3/src/backend/Headless.cpp#include <aquamarine/backend/Headless.hpp> #include <fcntl.h> #include <time.h> #include <sys/timerfd.h> #include <string.h> #include "Shared.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer #define TIMESPEC_NSEC_PER_SEC 1000000000L static void timespecAddNs(timespec* pTimespec, int64_t delta) { int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; pTimespec->tv_nsec += (long)delta_ns_low; if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++pTimespec->tv_sec; } } Aquamarine::CHeadlessOutput::CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CHeadlessBackend> backend_) : backend(backend_) { name = name_; framecb = makeShared<std::function<void()>>([this]() { frameScheduled = false; events.frame.emit(); }); } Aquamarine::CHeadlessOutput::~CHeadlessOutput() { backend->backend->removeIdleEvent(framecb); events.destroy.emit(); } bool Aquamarine::CHeadlessOutput::commit() { events.commit.emit(); state->onCommit(); needsFrame = false; return true; } bool Aquamarine::CHeadlessOutput::test() { return true; } std::vector<SDRMFormat> Aquamarine::CHeadlessOutput::getRenderFormats() { // not sure if this is right but prob doest matter return backend->getRenderFormats(); } Hyprutils::Memory::CSharedPointer<IBackendImplementation> Aquamarine::CHeadlessOutput::getBackend() { return backend.lock(); } void Aquamarine::CHeadlessOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CHeadlessOutput::scheduleFrame: reason {}, needsFrame {}, frameScheduled {}", (uint32_t)reason, needsFrame, frameScheduled))); // FIXME: limit fps to the committed framerate. needsFrame = true; if (frameScheduled) return; frameScheduled = true; backend->backend->addIdleEvent(framecb); } bool Aquamarine::CHeadlessOutput::destroy() { events.destroy.emit(); std::erase(backend->outputs, self.lock()); return true; } Aquamarine::CHeadlessBackend::~CHeadlessBackend() { ; } Aquamarine::CHeadlessBackend::CHeadlessBackend(SP<CBackend> backend_) : backend(backend_) { timers.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); } eBackendType Aquamarine::CHeadlessBackend::type() { return eBackendType::AQ_BACKEND_HEADLESS; } bool Aquamarine::CHeadlessBackend::start() { return true; } std::vector<SP<SPollFD>> Aquamarine::CHeadlessBackend::pollFDs() { return {makeShared<SPollFD>(timers.timerfd, [this]() { dispatchTimers(); })}; } int Aquamarine::CHeadlessBackend::drmFD() { return -1; } bool Aquamarine::CHeadlessBackend::dispatchEvents() { return true; } uint32_t Aquamarine::CHeadlessBackend::capabilities() { return 0; } bool Aquamarine::CHeadlessBackend::setCursor(SP<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::CHeadlessBackend::onReady() { ; } std::vector<SDRMFormat> Aquamarine::CHeadlessBackend::getRenderFormats() { // FIXME: allow any modifier. Maybe set INVALID to mean that? or a special value? return {SDRMFormat{.drmFormat = DRM_FORMAT_RGBA8888, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_RGBA1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}}; } std::vector<SDRMFormat> Aquamarine::CHeadlessBackend::getCursorFormats() { return {}; // No cursor support } bool Aquamarine::CHeadlessBackend::createOutput(const std::string& name) { auto output = SP<CHeadlessOutput>(new CHeadlessOutput(name.empty() ? std::format("HEADLESS-{}", ++outputIDCounter) : name, self.lock())); outputs.emplace_back(output); output->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); output->self = output; backend->events.newOutput.emit(SP<IOutput>(output)); return true; } void Aquamarine::CHeadlessBackend::dispatchTimers() { std::vector<CTimer> toFire; for (size_t i = 0; i < timers.timers.size(); ++i) { if (timers.timers.at(i).expired()) { toFire.emplace_back(timers.timers.at(i)); timers.timers.erase(timers.timers.begin() + i); i--; continue; } } for (auto& copy : toFire) { if (copy.what) copy.what(); } updateTimerFD(); } void Aquamarine::CHeadlessBackend::updateTimerFD() { long long lowestNs = TIMESPEC_NSEC_PER_SEC * 240 /* 240s, 4 mins */; const auto clocknow = std::chrono::steady_clock::now(); bool any = false; for (auto& t : timers.timers) { auto delta = std::chrono::duration_cast<std::chrono::microseconds>(t.when - clocknow).count() * 1000 /* µs -> ns */; if (delta < lowestNs) lowestNs = delta; } if (lowestNs < 0) lowestNs = 0; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecAddNs(&now, lowestNs); itimerspec ts = {.it_value = now}; if (timerfd_settime(timers.timerfd, TFD_TIMER_ABSTIME, &ts, nullptr)) backend->log(AQ_LOG_ERROR, std::format("headless: failed to arm timerfd: {}", strerror(errno))); } SP<IAllocator> Aquamarine::CHeadlessBackend::preferredAllocator() { return backend->primaryAllocator; } bool Aquamarine::CHeadlessBackend::CTimer::expired() { return std::chrono::steady_clock::now() > when; } 07070100000034000081A400000000000000000000000166C38D3F0000A522000000000000000000000000000000000000002900000000aquamarine-0.3.3/src/backend/Session.cpp#include <aquamarine/backend/Backend.hpp> extern "C" { #include <libseat.h> #include <libinput.h> #include <libudev.h> #include <cstring> #include <xf86drm.h> #include <sys/stat.h> #include <xf86drmMode.h> #include <linux/input.h> #include <unistd.h> } using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer static const std::string AQ_UNKNOWN_DEVICE_NAME = "UNKNOWN"; // we can't really do better with libseat/libinput logs // because they don't allow us to pass "data" or anything... // Nobody should create multiple backends anyways really Hyprutils::Memory::CSharedPointer<CBackend> backendInUse; // static Aquamarine::eBackendLogLevel logLevelFromLibseat(libseat_log_level level) { switch (level) { case LIBSEAT_LOG_LEVEL_ERROR: return AQ_LOG_ERROR; case LIBSEAT_LOG_LEVEL_SILENT: return AQ_LOG_TRACE; default: break; } return AQ_LOG_DEBUG; } static Aquamarine::eBackendLogLevel logLevelFromLibinput(libinput_log_priority level) { switch (level) { case LIBINPUT_LOG_PRIORITY_ERROR: return AQ_LOG_ERROR; default: break; } return AQ_LOG_DEBUG; } static void libseatLog(libseat_log_level level, const char* fmt, va_list args) { if (!backendInUse) return; static char string[1024]; vsnprintf(string, sizeof(string), fmt, args); backendInUse->log(logLevelFromLibseat(level), std::format("[libseat] {}", string)); } static void libinputLog(libinput*, libinput_log_priority level, const char* fmt, va_list args) { if (!backendInUse) return; static char string[1024]; vsnprintf(string, sizeof(string), fmt, args); backendInUse->log(logLevelFromLibinput(level), std::format("[libinput] {}", string)); } // ------------ Libseat static void libseatEnableSeat(struct libseat* seat, void* data) { auto PSESSION = (Aquamarine::CSession*)data; PSESSION->active = true; if (PSESSION->libinputHandle) libinput_resume(PSESSION->libinputHandle); PSESSION->events.changeActive.emit(); } static void libseatDisableSeat(struct libseat* seat, void* data) { auto PSESSION = (Aquamarine::CSession*)data; PSESSION->active = false; if (PSESSION->libinputHandle) libinput_suspend(PSESSION->libinputHandle); PSESSION->events.changeActive.emit(); libseat_disable_seat(PSESSION->libseatHandle); } static const libseat_seat_listener libseatListener = { .enable_seat = ::libseatEnableSeat, .disable_seat = ::libseatDisableSeat, }; // ------------ Libinput static int libinputOpen(const char* path, int flags, void* data) { auto SESSION = (CSession*)data; auto dev = makeShared<CSessionDevice>(SESSION->self.lock(), path); if (!dev->dev) return -1; SESSION->sessionDevices.emplace_back(dev); return dev->fd; } static void libinputClose(int fd, void* data) { auto SESSION = (CSession*)data; std::erase_if(SESSION->sessionDevices, [fd](const auto& dev) { auto toRemove = dev->fd == fd; if (toRemove) dev->events.remove.emit(); return toRemove; }); } static const libinput_interface libinputListener = { .open_restricted = ::libinputOpen, .close_restricted = ::libinputClose, }; // ------------ Aquamarine::CSessionDevice::CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_) : session(session_), path(path_) { deviceID = libseat_open_device(session->libseatHandle, path.c_str(), &fd); if (deviceID < 0) { session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't open device at {}", path_)); return; } struct stat stat_; if (fstat(fd, &stat_) < 0) { session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't stat device at {}", path_)); deviceID = -1; return; } dev = stat_.st_rdev; } Aquamarine::CSessionDevice::~CSessionDevice() { if (deviceID >= 0) if (libseat_close_device(session->libseatHandle, deviceID) < 0) session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't close device at {}", path)); if (fd >= 0) close(fd); } bool Aquamarine::CSessionDevice::supportsKMS() { if (deviceID < 0) return false; bool kms = drmIsKMS(fd); if (kms) session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} supports kms", path)); else session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} does not support kms", path)); return kms; } SP<CSessionDevice> Aquamarine::CSessionDevice::openIfKMS(SP<CSession> session_, const std::string& path_) { auto dev = makeShared<CSessionDevice>(session_, path_); if (!dev->supportsKMS()) return nullptr; return dev; } SP<CSession> Aquamarine::CSession::attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_) { if (!backend_) return nullptr; auto session = makeShared<CSession>(); session->backend = backend_; session->self = session; backendInUse = backend_; // ------------ Libseat libseat_set_log_handler(libseatLog); libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO); session->libseatHandle = libseat_open_seat(&libseatListener, session.get()); if (!session->libseatHandle) { session->backend->log(AQ_LOG_ERROR, "libseat: failed to open a seat"); return nullptr; } auto seatName = libseat_seat_name(session->libseatHandle); if (!seatName) { session->backend->log(AQ_LOG_ERROR, "libseat: failed to get seat name"); return nullptr; } session->seatName = seatName; // dispatch any already pending events session->dispatchPendingEventsAsync(); // ----------- Udev session->udevHandle = udev_new(); if (!session->udevHandle) { session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new context"); return nullptr; } session->udevMonitor = udev_monitor_new_from_netlink(session->udevHandle, "udev"); if (!session->udevMonitor) { session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new udevMonitor"); return nullptr; } udev_monitor_filter_add_match_subsystem_devtype(session->udevMonitor, "drm", nullptr); udev_monitor_enable_receiving(session->udevMonitor); // ----------- Libinput session->libinputHandle = libinput_udev_create_context(&libinputListener, session.get(), session->udevHandle); if (!session->libinputHandle) { session->backend->log(AQ_LOG_ERROR, "libinput: failed to create a new context"); return nullptr; } if (libinput_udev_assign_seat(session->libinputHandle, session->seatName.c_str())) { session->backend->log(AQ_LOG_ERROR, "libinput: failed to assign a seat"); return nullptr; } libinput_log_set_handler(session->libinputHandle, ::libinputLog); libinput_log_set_priority(session->libinputHandle, LIBINPUT_LOG_PRIORITY_DEBUG); return session; } Aquamarine::CSession::~CSession() { sessionDevices.clear(); libinputDevices.clear(); if (libinputHandle) libinput_unref(libinputHandle); if (libseatHandle) libseat_close_seat(libseatHandle); if (udevMonitor) udev_monitor_unref(udevMonitor); if (udevHandle) udev_unref(udevHandle); libseatHandle = nullptr; udevMonitor = nullptr; udevHandle = nullptr; } static bool isDRMCard(const char* sysname) { const char prefix[] = DRM_PRIMARY_MINOR_NAME; if (strncmp(sysname, prefix, strlen(prefix)) != 0) return false; for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { if (sysname[i] < '0' || sysname[i] > '9') return false; } return true; } void Aquamarine::CSession::onReady() { for (auto& d : libinputDevices) { if (d->keyboard) backend->events.newKeyboard.emit(SP<IKeyboard>(d->keyboard)); if (d->mouse) backend->events.newPointer.emit(SP<IPointer>(d->mouse)); if (d->touch) backend->events.newTouch.emit(SP<ITouch>(d->touch)); if (d->switchy) backend->events.newSwitch.emit(SP<ITouch>(d->touch)); if (d->tablet) backend->events.newTablet.emit(SP<ITablet>(d->tablet)); if (d->tabletPad) backend->events.newTabletPad.emit(SP<ITabletPad>(d->tabletPad)); for (auto& t : d->tabletTools) { backend->events.newTabletTool.emit(SP<ITabletTool>(t)); } } } void Aquamarine::CSession::dispatchUdevEvents() { if (!udevHandle || !udevMonitor) return; auto device = udev_monitor_receive_device(udevMonitor); if (!device) return; auto sysname = udev_device_get_sysname(device); auto devnode = udev_device_get_devnode(device); auto action = udev_device_get_action(device); backend->log(AQ_LOG_DEBUG, std::format("udev: new udev {} event for {}", action ? action : "unknown", sysname ? sysname : "unknown")); if (!isDRMCard(sysname) || !action || !devnode) { udev_device_unref(device); return; } dev_t deviceNum = udev_device_get_devnum(device); SP<CSessionDevice> sessionDevice; for (auto& sDev : sessionDevices) { if (sDev->dev == deviceNum) { sessionDevice = sDev; break; } } if (!sessionDevice) { udev_device_unref(device); return; } if (action == std::string{"add"}) events.addDrmCard.emit(SAddDrmCardEvent{.path = devnode}); else if (action == std::string{"change"}) { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} changed", sysname ? sysname : "unknown")); CSessionDevice::SChangeEvent event; // auto prop = udev_device_get_property_value(device, "HOTPLUG"); if (prop && prop == std::string{"1"}) { event.type = CSessionDevice::AQ_SESSION_EVENT_CHANGE_HOTPLUG; prop = udev_device_get_property_value(device, "CONNECTOR"); if (prop) event.hotplug.connectorID = std::stoull(prop); prop = udev_device_get_property_value(device, "PROPERTY"); if (prop) event.hotplug.propID = std::stoull(prop); } else if (prop = udev_device_get_property_value(device, "LEASE"); prop && prop == std::string{"1"}) { event.type = CSessionDevice::AQ_SESSION_EVENT_CHANGE_LEASE; } else { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} change event unrecognized", sysname ? sysname : "unknown")); } sessionDevice->events.change.emit(event); } else if (action == std::string{"remove"}) { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} removed", sysname ? sysname : "unknown")); sessionDevice->events.remove.emit(); } udev_device_unref(device); return; } void Aquamarine::CSession::dispatchLibinputEvents() { if (!libinputHandle) return; if (int ret = libinput_dispatch(libinputHandle); ret) { backend->log(AQ_LOG_ERROR, std::format("Couldn't dispatch libinput events: {}", strerror(-ret))); return; } libinput_event* event = libinput_get_event(libinputHandle); while (event) { handleLibinputEvent(event); libinput_event_destroy(event); event = libinput_get_event(libinputHandle); } } void Aquamarine::CSession::dispatchLibseatEvents() { if (libseat_dispatch(libseatHandle, 0) == -1) backend->log(AQ_LOG_ERROR, "Couldn't dispatch libseat events"); } void Aquamarine::CSession::dispatchPendingEventsAsync() { dispatchLibseatEvents(); dispatchUdevEvents(); dispatchLibinputEvents(); } std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> Aquamarine::CSession::pollFDs() { // clang-format off return { makeShared<SPollFD>(libseat_get_fd(libseatHandle), [this](){ dispatchLibseatEvents(); }), makeShared<SPollFD>(udev_monitor_get_fd(udevMonitor), [this](){ dispatchUdevEvents(); }), makeShared<SPollFD>(libinput_get_fd(libinputHandle), [this](){ dispatchLibinputEvents(); }) }; // clang-format on } bool Aquamarine::CSession::switchVT(uint32_t vt) { return libseat_switch_session(libseatHandle, vt) == 0; } void Aquamarine::CSession::handleLibinputEvent(libinput_event* e) { auto device = libinput_event_get_device(e); auto eventType = libinput_event_get_type(e); auto data = libinput_device_get_user_data(device); backend->log(AQ_LOG_TRACE, std::format("libinput: Event {}", (int)eventType)); if (!data && eventType != LIBINPUT_EVENT_DEVICE_ADDED) { backend->log(AQ_LOG_ERROR, "libinput: No aq device in event and not added"); return; } if (!data) { auto dev = libinputDevices.emplace_back(makeShared<CLibinputDevice>(device, self)); dev->self = dev; dev->init(); return; } auto hlDevice = ((CLibinputDevice*)data)->self.lock(); bool destroyTool = false; switch (eventType) { case LIBINPUT_EVENT_DEVICE_ADDED: /* shouldn't happen */ break; case LIBINPUT_EVENT_DEVICE_REMOVED: std::erase_if(libinputDevices, [device](const auto& d) { return d->device == device; }); break; // --------- keyboard case LIBINPUT_EVENT_KEYBOARD_KEY: { auto kbe = libinput_event_get_keyboard_event(e); hlDevice->keyboard->events.key.emit(IKeyboard::SKeyEvent{ .timeMs = (uint32_t)(libinput_event_keyboard_get_time_usec(kbe) / 1000), .key = libinput_event_keyboard_get_key(kbe), .pressed = libinput_event_keyboard_get_key_state(kbe) == LIBINPUT_KEY_STATE_PRESSED, }); break; } // --------- pointer case LIBINPUT_EVENT_POINTER_MOTION: { auto pe = libinput_event_get_pointer_event(e); hlDevice->mouse->events.move.emit(IPointer::SMoveEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .delta = {libinput_event_pointer_get_dx(pe), libinput_event_pointer_get_dy(pe)}, .unaccel = {libinput_event_pointer_get_dx_unaccelerated(pe), libinput_event_pointer_get_dy_unaccelerated(pe)}, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { auto pe = libinput_event_get_pointer_event(e); hlDevice->mouse->events.warp.emit(IPointer::SWarpEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .absolute = {libinput_event_pointer_get_absolute_x_transformed(pe, 1), libinput_event_pointer_get_absolute_y_transformed(pe, 1)}, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_BUTTON: { auto pe = libinput_event_get_pointer_event(e); const auto SEATCOUNT = libinput_event_pointer_get_seat_button_count(pe); const bool PRESSED = libinput_event_pointer_get_button_state(pe) == LIBINPUT_BUTTON_STATE_PRESSED; if ((PRESSED && SEATCOUNT != 1) || (!PRESSED && SEATCOUNT != 0)) break; hlDevice->mouse->events.button.emit(IPointer::SButtonEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .button = libinput_event_pointer_get_button(pe), .pressed = PRESSED, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: { auto pe = libinput_event_get_pointer_event(e); IPointer::SAxisEvent aqe = { .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), }; switch (eventType) { case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_WHEEL; break; case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_FINGER; break; case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_CONTINUOUS; break; default: break; /* unreachable */ } static const std::array<libinput_pointer_axis, 2> LAXES = { LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, }; for (auto& axis : LAXES) { if (!libinput_event_pointer_has_axis(pe, axis)) continue; aqe.axis = axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL ? IPointer::AQ_POINTER_AXIS_VERTICAL : IPointer::AQ_POINTER_AXIS_HORIZONTAL; aqe.delta = libinput_event_pointer_get_scroll_value(pe, axis); aqe.direction = IPointer::AQ_POINTER_AXIS_RELATIVE_IDENTICAL; if (libinput_device_config_scroll_get_natural_scroll_enabled(device)) aqe.direction = IPointer::AQ_POINTER_AXIS_RELATIVE_INVERTED; if (aqe.source == IPointer::AQ_POINTER_AXIS_SOURCE_WHEEL) aqe.discrete = libinput_event_pointer_get_scroll_value_v120(pe, axis); hlDevice->mouse->events.axis.emit(aqe); } hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeBegin.emit(IPointer::SSwipeBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeUpdate.emit(IPointer::SSwipeUpdateEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), .delta = {libinput_event_gesture_get_dx(ge), libinput_event_gesture_get_dy(ge)}, }); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeEnd.emit(IPointer::SSwipeEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchBegin.emit(IPointer::SPinchBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchUpdate.emit(IPointer::SPinchUpdateEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), .delta = {libinput_event_gesture_get_dx(ge), libinput_event_gesture_get_dy(ge)}, .scale = libinput_event_gesture_get_scale(ge), .rotation = libinput_event_gesture_get_angle_delta(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchEnd.emit(IPointer::SPinchEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.holdBegin.emit(IPointer::SHoldBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_HOLD_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.holdEnd.emit(IPointer::SHoldEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } // --------- touch case LIBINPUT_EVENT_TOUCH_DOWN: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.down.emit(ITouch::SDownEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), .pos = {libinput_event_touch_get_x_transformed(te, 1), libinput_event_touch_get_y_transformed(te, 1)}, }); break; } case LIBINPUT_EVENT_TOUCH_UP: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.up.emit(ITouch::SUpEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), }); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.move.emit(ITouch::SMotionEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), .pos = {libinput_event_touch_get_x_transformed(te, 1), libinput_event_touch_get_y_transformed(te, 1)}, }); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.cancel.emit(ITouch::SCancelEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), }); break; } case LIBINPUT_EVENT_TOUCH_FRAME: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.frame.emit(); break; } // --------- switch case LIBINPUT_EVENT_SWITCH_TOGGLE: { auto se = libinput_event_get_switch_event(e); const bool ENABLED = libinput_event_switch_get_switch_state(se) == LIBINPUT_SWITCH_STATE_ON; if (ENABLED == hlDevice->switchy->state) return; hlDevice->switchy->state = ENABLED; switch (libinput_event_switch_get_switch(se)) { case LIBINPUT_SWITCH_LID: hlDevice->switchy->type = ISwitch::AQ_SWITCH_TYPE_LID; break; case LIBINPUT_SWITCH_TABLET_MODE: hlDevice->switchy->type = ISwitch::AQ_SWITCH_TYPE_TABLET_MODE; break; } hlDevice->switchy->events.fire.emit(ISwitch::SFireEvent{ .timeMs = (uint32_t)(libinput_event_switch_get_time_usec(se) / 1000), .type = hlDevice->switchy->type, .enable = ENABLED, }); break; } // --------- tbalet case LIBINPUT_EVENT_TABLET_PAD_BUTTON: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.button.emit(ITabletPad::SButtonEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .button = libinput_event_tablet_pad_get_button_number(tpe), .down = libinput_event_tablet_pad_get_button_state(tpe) == LIBINPUT_BUTTON_STATE_PRESSED, .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), .group = (uint16_t)libinput_tablet_pad_mode_group_get_index(libinput_event_tablet_pad_get_mode_group(tpe)), }); break; } case LIBINPUT_EVENT_TABLET_PAD_RING: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.ring.emit(ITabletPad::SRingEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .source = libinput_event_tablet_pad_get_ring_source(tpe) == LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN ? ITabletPad::AQ_TABLET_PAD_RING_SOURCE_UNKNOWN : ITabletPad::AQ_TABLET_PAD_RING_SOURCE_FINGER, .ring = (uint16_t)libinput_event_tablet_pad_get_ring_number(tpe), .pos = libinput_event_tablet_pad_get_ring_position(tpe), .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), }); break; } case LIBINPUT_EVENT_TABLET_PAD_STRIP: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.strip.emit(ITabletPad::SStripEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .source = libinput_event_tablet_pad_get_strip_source(tpe) == LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN ? ITabletPad::AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN : ITabletPad::AQ_TABLET_PAD_STRIP_SOURCE_FINGER, .strip = (uint16_t)libinput_event_tablet_pad_get_strip_number(tpe), .pos = libinput_event_tablet_pad_get_strip_position(tpe), .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), }); break; } case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); hlDevice->tablet->events.proximity.emit(ITablet::SProximityEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .absolute = {libinput_event_tablet_tool_get_x_transformed(tte, 1), libinput_event_tablet_tool_get_y_transformed(tte, 1)}, .in = libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, }); destroyTool = !libinput_tablet_tool_is_unique(libinput_event_tablet_tool_get_tool(tte)) && libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT; if (libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { std::erase(hlDevice->tabletTools, tool); break; } // fallthrough. If this is proximity in, also process axis. } case LIBINPUT_EVENT_TABLET_TOOL_AXIS: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); ITablet::SAxisEvent event = { .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), }; if (libinput_event_tablet_tool_x_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_X; event.absolute.x = libinput_event_tablet_tool_get_x_transformed(tte, 1); event.delta.x = libinput_event_tablet_tool_get_dx(tte); } if (libinput_event_tablet_tool_y_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_Y; event.absolute.y = libinput_event_tablet_tool_get_y_transformed(tte, 1); event.delta.y = libinput_event_tablet_tool_get_dy(tte); } if (libinput_event_tablet_tool_pressure_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_PRESSURE; event.pressure = libinput_event_tablet_tool_get_pressure(tte); } if (libinput_event_tablet_tool_distance_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_DISTANCE; event.distance = libinput_event_tablet_tool_get_distance(tte); } if (libinput_event_tablet_tool_tilt_x_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_TILT_X; event.tilt.x = libinput_event_tablet_tool_get_tilt_x(tte); } if (libinput_event_tablet_tool_tilt_y_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_TILT_Y; event.tilt.y = libinput_event_tablet_tool_get_tilt_y(tte); } if (libinput_event_tablet_tool_rotation_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_ROTATION; event.rotation = libinput_event_tablet_tool_get_rotation(tte); } if (libinput_event_tablet_tool_slider_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_SLIDER; event.slider = libinput_event_tablet_tool_get_slider_position(tte); } if (libinput_event_tablet_tool_wheel_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_WHEEL; event.wheelDelta = libinput_event_tablet_tool_get_wheel_delta(tte); } hlDevice->tablet->events.axis.emit(event); if (destroyTool) std::erase(hlDevice->tabletTools, tool); break; } case LIBINPUT_EVENT_TABLET_TOOL_TIP: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); hlDevice->tablet->events.tip.emit(ITablet::STipEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .absolute = {libinput_event_tablet_tool_get_x_transformed(tte, 1), libinput_event_tablet_tool_get_y_transformed(tte, 1)}, .down = libinput_event_tablet_tool_get_tip_state(tte) == LIBINPUT_TABLET_TOOL_TIP_DOWN, }); break; } case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); hlDevice->tablet->events.button.emit(ITablet::SButtonEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .button = libinput_event_tablet_tool_get_button(tte), .down = libinput_event_tablet_tool_get_button_state(tte) == LIBINPUT_BUTTON_STATE_PRESSED, }); break; } default: break; } } Aquamarine::CLibinputDevice::CLibinputDevice(libinput_device* device_, Hyprutils::Memory::CWeakPointer<CSession> session_) : device(device_), session(session_) { ; } void Aquamarine::CLibinputDevice::init() { const auto VENDOR = libinput_device_get_id_vendor(device); const auto PRODUCT = libinput_device_get_id_product(device); const auto NAME = libinput_device_get_name(device); session->backend->log(AQ_LOG_DEBUG, std::format("libinput: New device {}: {}-{}", NAME ? NAME : "Unknown", VENDOR, PRODUCT)); name = NAME; libinput_device_ref(device); libinput_device_set_user_data(device, this); if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { keyboard = makeShared<CLibinputKeyboard>(self.lock()); if (session->backend->ready) session->backend->events.newKeyboard.emit(SP<IKeyboard>(keyboard)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { mouse = makeShared<CLibinputMouse>(self.lock()); if (session->backend->ready) session->backend->events.newPointer.emit(SP<IPointer>(mouse)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { touch = makeShared<CLibinputTouch>(self.lock()); if (session->backend->ready) session->backend->events.newTouch.emit(SP<ITouch>(touch)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_SWITCH)) { switchy = makeShared<CLibinputSwitch>(self.lock()); if (session->backend->ready) session->backend->events.newSwitch.emit(SP<ISwitch>(switchy)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { tablet = makeShared<CLibinputTablet>(self.lock()); if (session->backend->ready) session->backend->events.newTablet.emit(SP<ITablet>(tablet)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { tabletPad = makeShared<CLibinputTabletPad>(self.lock()); if (session->backend->ready) session->backend->events.newTabletPad.emit(SP<ITabletPad>(tabletPad)); } } Aquamarine::CLibinputDevice::~CLibinputDevice() { libinput_device_set_user_data(device, nullptr); libinput_device_unref(device); } SP<CLibinputTabletTool> Aquamarine::CLibinputDevice::toolFrom(libinput_tablet_tool* tool) { for (auto& t : tabletTools) { if (t->libinputTool == tool) return t; } auto newt = makeShared<CLibinputTabletTool>(self.lock(), tool); tabletTools.emplace_back(newt); if (session->backend->ready) session->backend->events.newTabletTool.emit(SP<ITabletTool>(newt)); return newt; } static ITabletTool::eTabletToolType aqTypeFromLibinput(libinput_tablet_tool_type value) { switch (value) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: return ITabletTool::AQ_TABLET_TOOL_TYPE_PEN; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: return ITabletTool::AQ_TABLET_TOOL_TYPE_ERASER; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: return ITabletTool::AQ_TABLET_TOOL_TYPE_BRUSH; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: return ITabletTool::AQ_TABLET_TOOL_TYPE_PENCIL; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: return ITabletTool::AQ_TABLET_TOOL_TYPE_AIRBRUSH; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: return ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE; case LIBINPUT_TABLET_TOOL_TYPE_LENS: return ITabletTool::AQ_TABLET_TOOL_TYPE_LENS; case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: return ITabletTool::AQ_TABLET_TOOL_TYPE_TOTEM; } return ITabletTool::AQ_TABLET_TOOL_TYPE_INVALID; } Aquamarine::CLibinputKeyboard::CLibinputKeyboard(SP<CLibinputDevice> dev) : device(dev) { libinput_device_led_update(device->device, (libinput_led)0); } libinput_device* Aquamarine::CLibinputKeyboard::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputKeyboard::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } void Aquamarine::CLibinputKeyboard::updateLEDs(uint32_t leds) { libinput_device_led_update(device->device, (libinput_led)leds); } Aquamarine::CLibinputMouse::CLibinputMouse(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev) : device(dev) { ; } libinput_device* Aquamarine::CLibinputMouse::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputMouse::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTouch::CLibinputTouch(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev) : device(dev) { double w = 0, h = 0; libinput_device_get_size(dev->device, &w, &h); physicalSize = {w, h}; } libinput_device* Aquamarine::CLibinputTouch::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTouch::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputSwitch::CLibinputSwitch(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev) : device(dev) { ; } libinput_device* Aquamarine::CLibinputSwitch::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputSwitch::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTablet::CLibinputTablet(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev) : device(dev) { if (libinput_device_get_id_bustype(device->device) == BUS_USB) { usbVendorID = libinput_device_get_id_vendor(device->device); usbProductID = libinput_device_get_id_product(device->device); } double w = 0, h = 0; libinput_device_get_size(dev->device, &w, &h); physicalSize = {w, h}; auto udevice = libinput_device_get_udev_device(device->device); paths.emplace_back(udev_device_get_syspath(udevice)); } libinput_device* Aquamarine::CLibinputTablet::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTablet::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTabletTool::CLibinputTabletTool(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev, libinput_tablet_tool* tool) : device(dev), libinputTool(tool) { type = aqTypeFromLibinput(libinput_tablet_tool_get_type(libinputTool)); serial = libinput_tablet_tool_get_serial(libinputTool); id = libinput_tablet_tool_get_tool_id(libinputTool); libinput_tablet_tool_ref(tool); capabilities = 0; if (libinput_tablet_tool_has_distance(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_DISTANCE; if (libinput_tablet_tool_has_pressure(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_PRESSURE; if (libinput_tablet_tool_has_tilt(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_TILT; if (libinput_tablet_tool_has_rotation(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_ROTATION; if (libinput_tablet_tool_has_slider(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_SLIDER; if (libinput_tablet_tool_has_wheel(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_WHEEL; libinput_tablet_tool_set_user_data(tool, this); } Aquamarine::CLibinputTabletTool::~CLibinputTabletTool() { libinput_tablet_tool_unref(libinputTool); } libinput_device* Aquamarine::CLibinputTabletTool::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTabletTool::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTabletPad::CLibinputTabletPad(Hyprutils::Memory::CSharedPointer<CLibinputDevice> dev) : device(dev) { buttons = libinput_device_tablet_pad_get_num_buttons(device->device); rings = libinput_device_tablet_pad_get_num_rings(device->device); strips = libinput_device_tablet_pad_get_num_strips(device->device); auto udevice = libinput_device_get_udev_device(device->device); paths.emplace_back(udev_device_get_syspath(udevice)); int groupsno = libinput_device_tablet_pad_get_num_mode_groups(device->device); for (size_t i = 0; i < groupsno; ++i) { auto g = createGroupFromID(i); if (g) groups.emplace_back(g); } } Aquamarine::CLibinputTabletPad::~CLibinputTabletPad() { int groups = libinput_device_tablet_pad_get_num_mode_groups(device->device); for (int i = 0; i < groups; ++i) { auto g = libinput_device_tablet_pad_get_mode_group(device->device, i); libinput_tablet_pad_mode_group_unref(g); } } libinput_device* Aquamarine::CLibinputTabletPad::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTabletPad::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } SP<ITabletPad::STabletPadGroup> Aquamarine::CLibinputTabletPad::createGroupFromID(int id) { auto libinputGroup = libinput_device_tablet_pad_get_mode_group(device->device, id); auto group = makeShared<STabletPadGroup>(); for (size_t i = 0; i < rings; ++i) { if (!libinput_tablet_pad_mode_group_has_ring(libinputGroup, i)) continue; group->rings.push_back(i); } for (size_t i = 0; i < strips; ++i) { if (!libinput_tablet_pad_mode_group_has_strip(libinputGroup, i)) continue; group->strips.push_back(i); } for (size_t i = 0; i < buttons; ++i) { if (!libinput_tablet_pad_mode_group_has_button(libinputGroup, i)) continue; group->buttons.push_back(i); } group->modes = libinput_tablet_pad_mode_group_get_num_modes(libinputGroup); return group; } 07070100000035000081A400000000000000000000000166C38D3F000073E9000000000000000000000000000000000000002900000000aquamarine-0.3.3/src/backend/Wayland.cpp#include <aquamarine/backend/Wayland.hpp> #include <wayland.hpp> #include <xdg-shell.hpp> #include "Shared.hpp" #include "FormatUtils.hpp" #include <string.h> #include <xf86drm.h> #include <gbm.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer static std::pair<int, std::string> openExclusiveShm() { // Only absolute paths can be shared across different shm_open() calls srand(time(nullptr)); std::string name = std::format("/aq{:x}", rand() % RAND_MAX); for (size_t i = 0; i < 69; ++i) { int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) return {fd, name}; } return {-1, ""}; } static int allocateSHMFile(size_t len) { auto [fd, name] = openExclusiveShm(); if (fd < 0) return -1; shm_unlink(name.c_str()); int ret; do { ret = ftruncate(fd, len); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } wl_shm_format shmFormatFromDRM(uint32_t drmFormat) { switch (drmFormat) { case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; default: return (wl_shm_format)drmFormat; } return (wl_shm_format)drmFormat; } Aquamarine::CWaylandBackend::~CWaylandBackend() { if (drmState.fd >= 0) close(drmState.fd); } eBackendType Aquamarine::CWaylandBackend::type() { return AQ_BACKEND_WAYLAND; } Aquamarine::CWaylandBackend::CWaylandBackend(SP<CBackend> backend_) : backend(backend_) { ; } bool Aquamarine::CWaylandBackend::start() { backend->log(AQ_LOG_DEBUG, "Starting the Wayland backend!"); waylandState.display = wl_display_connect(nullptr); if (!waylandState.display) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: wl_display_connect failed (is a wayland compositor running?)"); return false; } waylandState.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(waylandState.display)); backend->log(AQ_LOG_DEBUG, std::format("Got registry at 0x{:x}", (uintptr_t)waylandState.registry->resource())); waylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { TRACE(backend->log(AQ_LOG_TRACE, std::format(" | received global: {} (version {}) with id {}", name, version, id))); const std::string NAME = name; if (NAME == "wl_seat") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 9, id))); waylandState.seat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_seat_interface, 9)); initSeat(); } else if (NAME == "xdg_wm_base") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id))); waylandState.xdg = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &xdg_wm_base_interface, 6)); initShell(); } else if (NAME == "wl_compositor") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id))); waylandState.compositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_compositor_interface, 6)); } else if (NAME == "wl_shm") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 1, id))); waylandState.shm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_shm_interface, 1)); } else if (NAME == "zwp_linux_dmabuf_v1") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 4, id))); waylandState.dmabuf = makeShared<CCZwpLinuxDmabufV1>((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &zwp_linux_dmabuf_v1_interface, 4)); if (!initDmabuf()) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: zwp_linux_dmabuf_v1 init failed"); waylandState.dmabufFailed = true; } } }); waylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t id) { backend->log(AQ_LOG_DEBUG, std::format("Global {} removed", id)); }); wl_display_roundtrip(waylandState.display); if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat || !waylandState.dmabuf || waylandState.dmabufFailed || !waylandState.shm) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: Missing protocols"); return false; } dispatchEvents(); createOutput(); return true; } int Aquamarine::CWaylandBackend::drmFD() { return drmState.fd; } bool Aquamarine::CWaylandBackend::createOutput(const std::string& szName) { auto o = outputs.emplace_back(SP<CWaylandOutput>(new CWaylandOutput(szName.empty() ? std::format("WAYLAND-{}", ++lastOutputID) : szName, self))); o->self = o; if (backend->ready) o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); idleCallbacks.emplace_back([this, o]() { backend->events.newOutput.emit(SP<IOutput>(o)); }); return true; } std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> Aquamarine::CWaylandBackend::pollFDs() { if (!waylandState.display) return {}; return {makeShared<SPollFD>(wl_display_get_fd(waylandState.display), [this]() { dispatchEvents(); })}; } bool Aquamarine::CWaylandBackend::dispatchEvents() { wl_display_flush(waylandState.display); if (wl_display_prepare_read(waylandState.display) == 0) { wl_display_read_events(waylandState.display); wl_display_dispatch_pending(waylandState.display); } else wl_display_dispatch(waylandState.display); int ret = 0; do { ret = wl_display_dispatch_pending(waylandState.display); wl_display_flush(waylandState.display); } while (ret > 0); // dispatch frames if (backend->ready) { for (auto& f : idleCallbacks) { f(); } idleCallbacks.clear(); } return true; } uint32_t Aquamarine::CWaylandBackend::capabilities() { return AQ_BACKEND_CAPABILITY_POINTER; } bool Aquamarine::CWaylandBackend::setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { // TODO: return true; } void Aquamarine::CWaylandBackend::onReady() { for (auto& o : outputs) { o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); if (!o->swapchain) { backend->log(AQ_LOG_ERROR, std::format("Output {} failed: swapchain creation failed", o->name)); continue; } } } Aquamarine::CWaylandKeyboard::CWaylandKeyboard(SP<CCWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : keyboard(keyboard_), backend(backend_) { if (!keyboard->resource()) return; backend->backend->log(AQ_LOG_DEBUG, "New wayland keyboard wl_keyboard"); keyboard->setKey([this](CCWlKeyboard* r, uint32_t serial, uint32_t timeMs, uint32_t key, wl_keyboard_key_state state) { events.key.emit(SKeyEvent{ .timeMs = timeMs, .key = key, .pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED, }); }); keyboard->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { events.modifiers.emit(SModifiersEvent{ .depressed = depressed, .latched = latched, .locked = locked, .group = group, }); }); } Aquamarine::CWaylandKeyboard::~CWaylandKeyboard() { ; } const std::string& Aquamarine::CWaylandKeyboard::getName() { return name; } Aquamarine::CWaylandPointer::CWaylandPointer(SP<CCWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : pointer(pointer_), backend(backend_) { if (!pointer->resource()) return; backend->backend->log(AQ_LOG_DEBUG, "New wayland pointer wl_pointer"); pointer->setMotion([this](CCWlPointer* r, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { const auto STATE = backend->focusedOutput->state->state(); if (!backend->focusedOutput || (!STATE.mode && !STATE.customMode)) return; const Vector2D size = STATE.customMode ? STATE.customMode->pixelSize : STATE.mode->pixelSize; Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; local = local / size; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); events.warp.emit(SWarpEvent{ .timeMs = (uint32_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000), .absolute = local, }); }); pointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface, wl_fixed_t x, wl_fixed_t y) { backend->lastEnterSerial = serial; for (auto& o : backend->outputs) { if (o->waylandState.surface->resource() != surface) continue; backend->focusedOutput = o; backend->backend->log(AQ_LOG_DEBUG, std::format("[wayland] focus changed: {}", o->name)); o->onEnter(pointer, serial); break; } }); pointer->setLeave([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface) { for (auto& o : backend->outputs) { if (o->waylandState.surface->resource() != surface) continue; o->cursorState.serial = 0; } }); pointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t timeMs, uint32_t button, wl_pointer_button_state state) { events.button.emit(SButtonEvent{ .timeMs = timeMs, .button = button, .pressed = state == WL_POINTER_BUTTON_STATE_PRESSED, }); }); pointer->setAxis([this](CCWlPointer* r, uint32_t timeMs, wl_pointer_axis axis, wl_fixed_t value) { events.axis.emit(SAxisEvent{ .timeMs = timeMs, .axis = axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ? AQ_POINTER_AXIS_HORIZONTAL : AQ_POINTER_AXIS_VERTICAL, .delta = wl_fixed_to_double(value), }); }); pointer->setFrame([this](CCWlPointer* r) { events.frame.emit(); }); } Aquamarine::CWaylandPointer::~CWaylandPointer() { ; } const std::string& Aquamarine::CWaylandPointer::getName() { return name; } void Aquamarine::CWaylandBackend::initSeat() { waylandState.seat->setCapabilities([this](CCWlSeat* r, wl_seat_capability cap) { const bool HAS_KEYBOARD = ((uint32_t)cap) & WL_SEAT_CAPABILITY_KEYBOARD; const bool HAS_POINTER = ((uint32_t)cap) & WL_SEAT_CAPABILITY_POINTER; if (HAS_KEYBOARD && keyboards.empty()) { auto k = keyboards.emplace_back(makeShared<CWaylandKeyboard>(makeShared<CCWlKeyboard>(waylandState.seat->sendGetKeyboard()), self)); idleCallbacks.emplace_back([this, k]() { backend->events.newKeyboard.emit(SP<IKeyboard>(k)); }); } else if (!HAS_KEYBOARD && !keyboards.empty()) keyboards.clear(); if (HAS_POINTER && pointers.empty()) { auto p = pointers.emplace_back(makeShared<CWaylandPointer>(makeShared<CCWlPointer>(waylandState.seat->sendGetPointer()), self)); idleCallbacks.emplace_back([this, p]() { backend->events.newPointer.emit(SP<IPointer>(p)); }); } else if (!HAS_POINTER && !pointers.empty()) pointers.clear(); }); } void Aquamarine::CWaylandBackend::initShell() { waylandState.xdg->setPing([](CCXdgWmBase* r, uint32_t serial) { r->sendPong(serial); }); } bool Aquamarine::CWaylandBackend::initDmabuf() { waylandState.dmabufFeedback = makeShared<CCZwpLinuxDmabufFeedbackV1>(waylandState.dmabuf->sendGetDefaultFeedback()); if (!waylandState.dmabufFeedback) { backend->log(AQ_LOG_ERROR, "initDmabuf: failed to get default feedback"); return false; } waylandState.dmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) { // no-op backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got done"); }); waylandState.dmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* deviceArr) { backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got main device"); dev_t device; ASSERT(deviceArr->size == sizeof(device)); memcpy(&device, deviceArr->data, sizeof(device)); drmDevice* drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: drmGetDeviceFromDevId failed"); return; } const char* name = nullptr; if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) name = drmDev->nodes[DRM_NODE_RENDER]; else { // Likely a split display/render setup. Pick the primary node and hope // Mesa will open the right render node under-the-hood. ASSERT(drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)); name = drmDev->nodes[DRM_NODE_PRIMARY]; backend->log(AQ_LOG_WARNING, "zwp_linux_dmabuf_v1: DRM device has no render node, using primary."); } if (!name) { backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: no node name"); return; } drmState.nodeName = name; drmFreeDevice(&drmDev); backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got node {}", drmState.nodeName)); }); waylandState.dmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int32_t fd, uint32_t size) { #pragma pack(push, 1) struct wlDrmFormatMarshalled { uint32_t drmFormat; char pad[4]; uint64_t modifier; }; #pragma pack(pop) static_assert(sizeof(wlDrmFormatMarshalled) == 16); auto formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (formatTable == MAP_FAILED) { backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to mmap the format table")); return; } const auto FORMATS = (wlDrmFormatMarshalled*)formatTable; for (size_t i = 0; i < size / 16; ++i) { auto& fmt = FORMATS[i]; auto modName = drmGetFormatModifierName(fmt.modifier); backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got format {} with modifier {}", fourccToName(fmt.drmFormat), modName ? modName : "UNKNOWN")); free(modName); auto it = std::find_if(dmabufFormats.begin(), dmabufFormats.end(), [&fmt](const auto& e) { return e.drmFormat == fmt.drmFormat; }); if (it == dmabufFormats.end()) { dmabufFormats.emplace_back(SDRMFormat{.drmFormat = fmt.drmFormat, .modifiers = {fmt.modifier}}); continue; } it->modifiers.emplace_back(fmt.modifier); } munmap(formatTable, size); }); wl_display_roundtrip(waylandState.display); if (!drmState.nodeName.empty()) { drmState.fd = open(drmState.nodeName.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC); if (drmState.fd < 0) { backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to open node {}", drmState.nodeName)); return false; } backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: opened node {} with fd {}", drmState.nodeName, drmState.fd)); } return true; } std::vector<SDRMFormat> Aquamarine::CWaylandBackend::getRenderFormats() { return dmabufFormats; } std::vector<SDRMFormat> Aquamarine::CWaylandBackend::getCursorFormats() { return dmabufFormats; } SP<IAllocator> Aquamarine::CWaylandBackend::preferredAllocator() { return backend->primaryAllocator; } Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : backend(backend_) { name = name_; waylandState.surface = makeShared<CCWlSurface>(backend->waylandState.compositor->sendCreateSurface()); if (!waylandState.surface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no surface given. Errno: {}", name, errno)); return; } waylandState.xdgSurface = makeShared<CCXdgSurface>(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource())); if (!waylandState.xdgSurface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgSurface given. Errno: {}", name, errno)); return; } waylandState.xdgSurface->setConfigure([this](CCXdgSurface* r, uint32_t serial) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure surface with {}", name, serial)); r->sendAckConfigure(serial); }); waylandState.xdgToplevel = makeShared<CCXdgToplevel>(waylandState.xdgSurface->sendGetToplevel()); if (!waylandState.xdgToplevel->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgToplevel given. Errno: {}", name, errno)); return; } waylandState.xdgToplevel->setWmCapabilities( [this](CCXdgToplevel* r, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: wm_capabilities received", name)); }); waylandState.xdgToplevel->setConfigure([this](CCXdgToplevel* r, int32_t w, int32_t h, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure toplevel with {}x{}", name, w, h)); events.state.emit(SStateEvent{.size = {w, h}}); sendFrameAndSetCallback(); }); waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { destroy(); }); waylandState.xdgToplevel->sendSetTitle(std::format("aquamarine - {}", name).c_str()); waylandState.xdgToplevel->sendSetAppId("aquamarine"); auto inputRegion = makeShared<CCWlRegion>(backend->waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); waylandState.surface->sendSetInputRegion(inputRegion.get()); waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); inputRegion->sendDestroy(); backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: initialized", name)); } Aquamarine::CWaylandOutput::~CWaylandOutput() { backend->idleCallbacks.clear(); // FIXME: mega hack to avoid a UAF in frame events events.destroy.emit(); if (waylandState.xdgToplevel) waylandState.xdgToplevel->sendDestroy(); if (waylandState.xdgSurface) waylandState.xdgSurface->sendDestroy(); if (waylandState.surface) waylandState.surface->sendDestroy(); } std::vector<SDRMFormat> Aquamarine::CWaylandOutput::getRenderFormats() { // TODO // this is technically wrong because this returns the format table formats // the actually supported formats are given by tranche formats return backend->getRenderFormats(); } bool Aquamarine::CWaylandOutput::destroy() { events.destroy.emit(); waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); waylandState.frameCallback.reset(); std::erase(backend->outputs, self.lock()); return true; } bool Aquamarine::CWaylandOutput::test() { return true; // TODO: } bool Aquamarine::CWaylandOutput::commit() { Vector2D pixelSize = {}; uint32_t refreshRate = 0; if (state->internalState.customMode) pixelSize = state->internalState.customMode->pixelSize; else if (state->internalState.mode) pixelSize = state->internalState.mode->pixelSize; else { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid mode", name)); return false; } uint32_t format = state->internalState.drmFormat; if (format == DRM_FORMAT_INVALID) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid format", name)); return false; } if (!swapchain) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: no swapchain, lying because it will soon be here", name)); return true; } if (!swapchain->reconfigure(SSwapchainOptions{.length = 2, .size = pixelSize, .format = format})) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: swapchain failed reconfiguring", name)); return false; } if (!state->internalState.buffer) { // if the consumer explicitly committed a null buffer, that's a violation. if (state->internalState.committed & COutputState::AQ_OUTPUT_STATE_BUFFER) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no buffer", name)); return false; } events.commit.emit(); state->onCommit(); return true; } auto wlBuffer = wlBufferFromBuffer(state->internalState.buffer); if (!wlBuffer) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no wlBuffer??", name)); return false; } if (wlBuffer->pendingRelease) backend->backend->log(AQ_LOG_WARNING, std::format("Output {}: pending state has a non-released buffer??", name)); wlBuffer->pendingRelease = true; waylandState.surface->sendAttach(wlBuffer->waylandState.buffer.get(), 0, 0); waylandState.surface->sendDamageBuffer(0, 0, INT32_MAX, INT32_MAX); waylandState.surface->sendCommit(); readyForFrameCallback = true; events.commit.emit(); state->onCommit(); needsFrame = false; return true; } SP<IBackendImplementation> Aquamarine::CWaylandOutput::getBackend() { return SP<IBackendImplementation>(backend.lock()); } SP<CWaylandBuffer> Aquamarine::CWaylandOutput::wlBufferFromBuffer(SP<IBuffer> buffer) { std::erase_if(backendState.buffers, [this](const auto& el) { return el.first.expired() || !swapchain->contains(el.first.lock()); }); for (auto& [k, v] : backendState.buffers) { if (k != buffer) continue; return v; } // create a new one auto wlBuffer = makeShared<CWaylandBuffer>(buffer, backend); if (!wlBuffer->good()) return nullptr; backendState.buffers.emplace_back(std::make_pair<>(buffer, wlBuffer)); return wlBuffer; } void Aquamarine::CWaylandOutput::sendFrameAndSetCallback() { events.frame.emit(); frameScheduled = false; if (waylandState.frameCallback || !readyForFrameCallback) return; waylandState.frameCallback = makeShared<CCWlCallback>(waylandState.surface->sendFrame()); waylandState.frameCallback->setDone([this](CCWlCallback* r, uint32_t ms) { onFrameDone(); }); } void Aquamarine::CWaylandOutput::onFrameDone() { waylandState.frameCallback.reset(); readyForFrameCallback = false; // FIXME: this is wrong, but otherwise we get bugs. // thanks @phonetic112 scheduleFrame(AQ_SCHEDULE_NEEDS_FRAME); if (frameScheduledWhileWaiting) sendFrameAndSetCallback(); else events.frame.emit(); frameScheduledWhileWaiting = false; } bool Aquamarine::CWaylandOutput::setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { if (!cursorState.cursorSurface) cursorState.cursorSurface = makeShared<CCWlSurface>(backend->waylandState.compositor->sendCreateSurface()); if (!cursorState.cursorSurface) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a wl_surface for the cursor", name)); return false; } if (!buffer) { cursorState.cursorBuffer.reset(); cursorState.cursorWlBuffer.reset(); backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, nullptr, cursorState.hotspot.x, cursorState.hotspot.y); return true; } cursorState.cursorBuffer = buffer; cursorState.hotspot = hotspot; if (buffer->shm().success) { auto attrs = buffer->shm(); auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); int fd = allocateSHMFile(bufLen); if (fd < 0) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to allocate a shm file", name)); return false; } void* data = mmap(nullptr, bufLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to mmap the cursor pixel data", name)); close(fd); return false; } memcpy(data, pixelData, bufLen); munmap(data, bufLen); auto pool = makeShared<CCWlShmPool>(backend->waylandState.shm->sendCreatePool(fd, bufLen)); if (!pool) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to submit a wl_shm pool", name)); close(fd); return false; } cursorState.cursorWlBuffer = makeShared<CCWlBuffer>(pool->sendCreateBuffer(0, attrs.size.x, attrs.size.y, attrs.stride, shmFormatFromDRM(attrs.format))); pool.reset(); close(fd); } else if (auto attrs = buffer->dmabuf(); attrs.success) { auto params = makeShared<CCZwpLinuxBufferParamsV1>(backend->waylandState.dmabuf->sendCreateParams()); for (int i = 0; i < attrs.planes; ++i) { params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); } cursorState.cursorWlBuffer = makeShared<CCWlBuffer>(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); } else { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor: No known attrs (tried dmabuf / shm)", name)); return false; } if (!cursorState.cursorWlBuffer) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor", name)); return false; } cursorState.cursorSurface->sendSetBufferScale(1); cursorState.cursorSurface->sendSetBufferTransform(WL_OUTPUT_TRANSFORM_NORMAL); cursorState.cursorSurface->sendAttach(cursorState.cursorWlBuffer.get(), 0, 0); cursorState.cursorSurface->sendDamage(0, 0, INT32_MAX, INT32_MAX); cursorState.cursorSurface->sendCommit(); // this may fail if we are not in focus if (!backend->pointers.empty() && cursorState.serial) backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); return true; } void Aquamarine::CWaylandOutput::moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipShedule) { return; } void Aquamarine::CWaylandOutput::onEnter(SP<CCWlPointer> pointer, uint32_t serial) { cursorState.serial = serial; if (!cursorState.cursorSurface) return; pointer->sendSetCursor(serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); } Hyprutils::Math::Vector2D Aquamarine::CWaylandOutput::cursorPlaneSize() { return {-1, -1}; // no limit } void Aquamarine::CWaylandOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CWaylandOutput::scheduleFrame: reason {}, needsFrame {}, frameScheduled {}", (uint32_t)reason, needsFrame, frameScheduled))); needsFrame = true; if (frameScheduled) return; frameScheduled = true; if (waylandState.frameCallback) frameScheduledWhileWaiting = true; else backend->idleCallbacks.emplace_back([this]() { sendFrameAndSetCallback(); }); } Aquamarine::CWaylandBuffer::CWaylandBuffer(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : buffer(buffer_), backend(backend_) { auto params = makeShared<CCZwpLinuxBufferParamsV1>(backend->waylandState.dmabuf->sendCreateParams()); if (!params) { backend->backend->log(AQ_LOG_ERROR, "WaylandBuffer: failed to query params"); return; } auto attrs = buffer->dmabuf(); for (size_t i = 0; i < attrs.planes; ++i) { params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); } waylandState.buffer = makeShared<CCWlBuffer>(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); waylandState.buffer->setRelease([this](CCWlBuffer* r) { pendingRelease = false; }); params->sendDestroy(); } Aquamarine::CWaylandBuffer::~CWaylandBuffer() { if (waylandState.buffer && waylandState.buffer->resource()) waylandState.buffer->sendDestroy(); } bool Aquamarine::CWaylandBuffer::good() { return waylandState.buffer && waylandState.buffer->resource(); } 07070100000036000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002100000000aquamarine-0.3.3/src/backend/drm07070100000037000081A400000000000000000000000166C38D3F0001186E000000000000000000000000000000000000002900000000aquamarine-0.3.3/src/backend/drm/DRM.cpp#include "aquamarine/output/Output.hpp" #include <aquamarine/backend/DRM.hpp> #include <aquamarine/backend/drm/Legacy.hpp> #include <aquamarine/backend/drm/Atomic.hpp> #include <aquamarine/allocator/GBM.hpp> #include <hyprutils/string/VarList.hpp> #include <chrono> #include <thread> #include <deque> #include <cstring> #include <filesystem> #include <system_error> #include <sys/mman.h> #include <fcntl.h> extern "C" { #include <libseat.h> #include <libudev.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <libdisplay-info/cvt.h> #include <libdisplay-info/info.h> #include <libdisplay-info/edid.h> } #include "Props.hpp" #include "FormatUtils.hpp" #include "Shared.hpp" #include "hwdata.hpp" #include "Renderer.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CDRMBackend::CDRMBackend(SP<CBackend> backend_) : backend(backend_) { listeners.sessionActivate = backend->session->events.changeActive.registerListener([this](std::any d) { if (backend->session->active) { // session got activated, we need to restore restoreAfterVT(); } }); } static udev_enumerate* enumDRMCards(udev* udev) { auto enumerate = udev_enumerate_new(udev); if (!enumerate) return nullptr; udev_enumerate_add_match_subsystem(enumerate, "drm"); udev_enumerate_add_match_sysname(enumerate, DRM_PRIMARY_MINOR_NAME "[0-9]*"); if (udev_enumerate_scan_devices(enumerate)) { udev_enumerate_unref(enumerate); return nullptr; } return enumerate; } static std::vector<SP<CSessionDevice>> scanGPUs(SP<CBackend> backend) { auto enumerate = enumDRMCards(backend->session->udevHandle); if (!enumerate) { backend->log(AQ_LOG_ERROR, "drm: couldn't enumerate gpus with udev"); return {}; } if (!udev_enumerate_get_list_entry(enumerate)) { // TODO: wait for them. backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs."); return {}; } udev_list_entry* entry = nullptr; size_t i = 0; std::deque<SP<CSessionDevice>> devices; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) { auto path = udev_list_entry_get_name(entry); auto device = udev_device_new_from_syspath(backend->session->udevHandle, path); if (!device) { backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {}", path ? path : "unknown")); continue; } backend->log(AQ_LOG_DEBUG, std::format("drm: Enumerated device {}", path ? path : "unknown")); auto seat = udev_device_get_property_value(device, "ID_SEAT"); if (!seat) seat = "seat0"; if (!backend->session->seatName.empty() && backend->session->seatName != seat) { backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {} because seat {} doesn't match our {}", path ? path : "unknown", seat, backend->session->seatName)); udev_device_unref(device); continue; } auto pciDevice = udev_device_get_parent_with_subsystem_devtype(device, "pci", nullptr); bool isBootVGA = false; if (pciDevice) { auto id = udev_device_get_sysattr_value(pciDevice, "boot_vga"); isBootVGA = id && id == std::string{"1"}; } if (!udev_device_get_devnode(device)) { backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, no devnode", path ? path : "unknown")); udev_device_unref(device); continue; } auto sessionDevice = CSessionDevice::openIfKMS(backend->session, udev_device_get_devnode(device)); if (!sessionDevice) { backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, not a KMS device", path ? path : "unknown")); udev_device_unref(device); continue; } udev_device_unref(device); if (isBootVGA) devices.push_front(sessionDevice); else devices.push_back(sessionDevice); ++i; } udev_enumerate_unref(enumerate); std::vector<SP<CSessionDevice>> vecDevices; auto explicitGpus = getenv("AQ_DRM_DEVICES"); if (explicitGpus) { backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit device list {}", explicitGpus)); Hyprutils::String::CVarList explicitDevices(explicitGpus, 0, ':', true); // Iterate over GPUs and canonicalize the paths for (auto& d : explicitDevices) { std::error_code ec; auto canonicalFilePath = std::filesystem::canonical(d, ec); // If there is an error, log and continue. // TODO: Verify that the path is a valid DRM device. (https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/backend/session/session.c?ref_type=heads#L369-387) if (ec) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to canonicalize path {}", d)); continue; } d = canonicalFilePath.string(); } for (auto& d : explicitDevices) { bool found = false; for (auto& vd : devices) { if (vd->path == d) { vecDevices.emplace_back(vd); found = true; break; } } if (found) backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit device {} found", d)); else backend->log(AQ_LOG_ERROR, std::format("drm: Explicit device {} not found", d)); } } else { for (auto& d : devices) { vecDevices.push_back(d); } } return vecDevices; } std::vector<SP<CDRMBackend>> Aquamarine::CDRMBackend::attempt(SP<CBackend> backend) { if (!backend->session) backend->session = CSession::attempt(backend); if (!backend->session) { backend->log(AQ_LOG_ERROR, "Failed to open a session"); return {}; } if (!backend->session->active) { backend->log(AQ_LOG_DEBUG, "Session is not active, waiting for 5s"); auto started = std::chrono::system_clock::now(); while (!backend->session->active) { std::this_thread::sleep_for(std::chrono::milliseconds(250)); backend->session->dispatchPendingEventsAsync(); if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - started).count() >= 5000) { backend->log(AQ_LOG_DEBUG, "Session timeout reached"); break; } } if (!backend->session->active) { backend->log(AQ_LOG_DEBUG, "Session could not be activated in time"); return {}; } } auto gpus = scanGPUs(backend); if (gpus.empty()) { backend->log(AQ_LOG_ERROR, "drm: Found no gpus to use, cannot continue"); return {}; } backend->log(AQ_LOG_DEBUG, std::format("drm: Found {} GPUs", gpus.size())); std::vector<SP<CDRMBackend>> backends; SP<CDRMBackend> newPrimary; for (auto& gpu : gpus) { auto drmBackend = SP<CDRMBackend>(new CDRMBackend(backend)); drmBackend->self = drmBackend; if (!drmBackend->registerGPU(gpu, newPrimary)) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu {}", gpu->path)); continue; } else backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu {}", gpu->path)); // TODO: consider listening for new devices // But if you expect me to handle gpu hotswaps you are probably insane LOL if (!drmBackend->checkFeatures()) { backend->log(AQ_LOG_ERROR, "drm: Failed checking features"); continue; } if (!drmBackend->initResources()) { backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources"); continue; } backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpu->path)); drmBackend->grabFormats(); drmBackend->scanConnectors(); drmBackend->recheckCRTCs(); if (!newPrimary) { backend->log(AQ_LOG_DEBUG, std::format("drm: gpu {} becomes primary drm", gpu->path)); newPrimary = drmBackend; } backends.emplace_back(drmBackend); // so that session can handle udev change/remove events for this gpu backend->session->sessionDevices.push_back(gpu); } return backends; } Aquamarine::CDRMBackend::~CDRMBackend() { ; } void Aquamarine::CDRMBackend::log(eBackendLogLevel l, const std::string& s) { backend->log(l, s); } bool Aquamarine::CDRMBackend::sessionActive() { return backend->session->active; } void Aquamarine::CDRMBackend::restoreAfterVT() { backend->log(AQ_LOG_DEBUG, "drm: Restoring after VT switch"); scanConnectors(); recheckCRTCs(); backend->log(AQ_LOG_DEBUG, "drm: Rescanned connectors"); if (!impl->reset()) backend->log(AQ_LOG_ERROR, "drm: failed reset"); std::vector<SP<SDRMConnector>> noMode; for (auto& c : connectors) { if (!c->crtc || !c->output) continue; SDRMConnectorCommitData data = { .mainFB = nullptr, .modeset = true, .blocking = true, .flags = 0, .test = false, }; auto& STATE = c->output->state->state(); auto& MODE = STATE.customMode ? STATE.customMode : STATE.mode; if (!MODE) { backend->log(AQ_LOG_WARNING, "drm: Connector {} has output but state has no mode, will send a reset state event later."); noMode.emplace_back(c); continue; } if (MODE->modeInfo.has_value()) data.modeInfo = *MODE->modeInfo; else data.calculateMode(c); if (STATE.buffer) { SP<CDRMFB> drmFB; auto buf = STATE.buffer; bool isNew = false; drmFB = CDRMFB::create(buf, self, &isNew); if (!drmFB) backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); data.mainFB = drmFB; } if (c->crtc->pendingCursor) data.cursorFB = c->crtc->pendingCursor; if (data.cursorFB && data.cursorFB->buffer->dmabuf().modifier == DRM_FORMAT_MOD_INVALID) data.cursorFB = nullptr; backend->log(AQ_LOG_DEBUG, std::format("drm: Restoring crtc {} with clock {} hdisplay {} vdisplay {} vrefresh {}", c->crtc->id, data.modeInfo.clock, data.modeInfo.hdisplay, data.modeInfo.vdisplay, data.modeInfo.vrefresh)); if (!impl->commit(c, data)) backend->log(AQ_LOG_ERROR, std::format("drm: crtc {} failed restore", c->crtc->id)); } for (auto& c : noMode) { if (!c->output) continue; // tell the consumer to re-set a state because we had no mode c->output->events.state.emit(IOutput::SStateEvent{}); } } bool Aquamarine::CDRMBackend::checkFeatures() { uint64_t curW = 0, curH = 0; if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW)) curW = 64; if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_HEIGHT, &curH)) curH = 64; drmProps.cursorSize = Hyprutils::Math::Vector2D{(double)curW, (double)curH}; uint64_t cap = 0; if (drmGetCap(gpu->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); return false; } if (drmGetCap(gpu->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CAP_CRTC_IN_VBLANK_EVENT unsupported")); return false; } if (drmGetCap(gpu->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); return false; } if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CLIENT_CAP_UNIVERSAL_PLANES unsupported")); return false; } drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; drmProps.supportsAddFb2Modifiers = drmGetCap(gpu->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap == 1; drmProps.supportsTimelines = drmGetCap(gpu->fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap) == 0 && cap == 1; if (envEnabled("AQ_NO_ATOMIC")) { backend->log(AQ_LOG_WARNING, "drm: AQ_NO_ATOMIC enabled, using the legacy drm iface"); impl = makeShared<CDRMLegacyImpl>(self.lock()); } else if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { backend->log(AQ_LOG_WARNING, "drm: failed to set DRM_CLIENT_CAP_ATOMIC, falling back to legacy"); impl = makeShared<CDRMLegacyImpl>(self.lock()); } else { backend->log(AQ_LOG_DEBUG, "drm: Atomic supported, using atomic for modesetting"); impl = makeShared<CDRMAtomicImpl>(self.lock()); drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; atomic = true; } backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAsyncCommit: {}", drmProps.supportsAsyncCommit)); backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAddFb2Modifiers: {}", drmProps.supportsAddFb2Modifiers)); backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsTimelines: {}", drmProps.supportsTimelines)); // TODO: allow no-modifiers? return true; } bool Aquamarine::CDRMBackend::initResources() { auto resources = drmModeGetResources(gpu->fd); if (!resources) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed")); return false; } backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs)); for (size_t i = 0; i < resources->count_crtcs; ++i) { auto CRTC = makeShared<SDRMCRTC>(); CRTC->id = resources->crtcs[i]; CRTC->backend = self; auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id); if (!drmCRTC) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id)); drmModeFreeResources(resources); crtcs.clear(); return false; } CRTC->legacy.gammaSize = drmCRTC->gamma_size; drmModeFreeCrtc(drmCRTC); if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) { backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id)); drmModeFreeResources(resources); crtcs.clear(); return false; } crtcs.emplace_back(CRTC); } if (crtcs.size() > 32) { backend->log(AQ_LOG_CRITICAL, "drm: Cannot support more than 32 CRTCs"); return false; } // initialize planes auto planeResources = drmModeGetPlaneResources(gpu->fd); if (!planeResources) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed")); return false; } backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes)); for (uint32_t i = 0; i < planeResources->count_planes; ++i) { auto id = planeResources->planes[i]; auto plane = drmModeGetPlane(gpu->fd, id); if (!plane) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id)); drmModeFreeResources(resources); crtcs.clear(); planes.clear(); return false; } auto aqPlane = makeShared<SDRMPlane>(); aqPlane->backend = self; aqPlane->self = aqPlane; if (!aqPlane->init((drmModePlane*)plane)) { backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id)); drmModeFreeResources(resources); crtcs.clear(); planes.clear(); return false; } planes.emplace_back(aqPlane); drmModeFreePlane(plane); } drmModeFreePlaneResources(planeResources); drmModeFreeResources(resources); return true; } bool Aquamarine::CDRMBackend::shouldBlit() { return primary; } bool Aquamarine::CDRMBackend::initMgpu() { SP<CGBMAllocator> newAllocator; if (primary || backend->primaryAllocator->type() != AQ_ALLOCATOR_TYPE_GBM) { newAllocator = CGBMAllocator::create(backend->reopenDRMNode(gpu->fd), backend); rendererState.allocator = newAllocator; } else { newAllocator = ((CGBMAllocator*)backend->primaryAllocator.get())->self.lock(); rendererState.allocator = newAllocator; } if (!rendererState.allocator) { backend->log(AQ_LOG_ERROR, "drm: initMgpu: no allocator"); return false; } rendererState.renderer = CDRMRenderer::attempt(newAllocator, backend.lock()); if (!rendererState.renderer) { backend->log(AQ_LOG_ERROR, "drm: initMgpu: no renderer"); return false; } rendererState.renderer->self = rendererState.renderer; buildGlFormats(rendererState.renderer->formats); return true; } void Aquamarine::CDRMBackend::buildGlFormats(const std::vector<SGLFormat>& fmts) { std::vector<SDRMFormat> result; for (auto& fmt : fmts) { if (fmt.external) continue; if (auto it = std::find_if(result.begin(), result.end(), [fmt](const auto& e) { return fmt.drmFormat == e.drmFormat; }); it != result.end()) { it->modifiers.emplace_back(fmt.modifier); continue; } result.emplace_back(SDRMFormat{ fmt.drmFormat, {fmt.modifier}, }); } glFormats = result; } void Aquamarine::CDRMBackend::recheckCRTCs() { if (connectors.empty() || crtcs.empty()) return; backend->log(AQ_LOG_DEBUG, "drm: Rechecking CRTCs"); std::vector<SP<SDRMConnector>> recheck, changed; for (auto& c : connectors) { if (c->crtc && c->status == DRM_MODE_CONNECTED) { backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping connector {}, has crtc {} and is connected", c->szName, c->crtc->id)); continue; } recheck.emplace_back(c); backend->log(AQ_LOG_DEBUG, std::format("drm: connector {}, has crtc {}, will be rechecked", c->szName, c->crtc ? (int)c->crtc->id : -1)); } for (size_t i = 0; i < crtcs.size(); ++i) { bool taken = false; for (auto& c : connectors) { if (c->crtc != crtcs.at(i)) continue; if (c->status != DRM_MODE_CONNECTED) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: slot {} crtc {} taken by {}, skipping", i, c->crtc->id, c->szName)); taken = true; break; } if (taken) continue; bool assigned = false; // try to use a connected connector for (auto& c : recheck) { if (!(c->possibleCrtcs & (1 << i))) continue; if (c->status != DRM_MODE_CONNECTED) continue; // deactivate old output if (c->output && c->output->state && c->output->state->state().enabled) { c->output->state->setEnabled(false); c->output->commit(); } backend->log(AQ_LOG_DEBUG, std::format("drm: connected slot {} crtc {} assigned to {}{}", i, crtcs.at(i)->id, c->szName, c->crtc ? std::format(" (old {})", c->crtc->id) : "")); c->crtc = crtcs.at(i); assigned = true; changed.emplace_back(c); std::erase(recheck, c); break; } if (!assigned) backend->log(AQ_LOG_DEBUG, std::format("drm: slot {} crtc {} unassigned", i, crtcs.at(i)->id)); } for (auto& c : connectors) { if (c->status == DRM_MODE_CONNECTED) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} is not connected{}", c->szName, c->crtc ? std::format(", removing old crtc {}", c->crtc->id) : "")); } // if any connectors get a crtc and are connected, we need to rescan to assign them outputs. bool rescan = false; for (auto& c : changed) { if (!c->output && c->status == DRM_MODE_CONNECTED) { rescan = true; continue; } // tell the user to re-assign a valid mode etc if (c->output) c->output->events.state.emit(IOutput::SStateEvent{}); } backend->log(AQ_LOG_DEBUG, "drm: rescanning after realloc"); scanConnectors(); } bool Aquamarine::CDRMBackend::grabFormats() { // FIXME: do this properly maybe? return true; } bool Aquamarine::CDRMBackend::registerGPU(SP<CSessionDevice> gpu_, SP<CDRMBackend> primary_) { gpu = gpu_; primary = primary_; auto drmName = drmGetDeviceNameFromFd2(gpu->fd); auto drmVer = drmGetVersion(gpu->fd); gpuName = drmName; auto drmVerName = drmVer->name ? drmVer->name : "unknown"; if (std::string_view(drmVerName) == "evdi") primary = {}; backend->log(AQ_LOG_DEBUG, std::format("drm: Starting backend for {}, with driver {}{}", drmName ? drmName : "unknown", drmVerName, (primary ? std::format(" with primary {}", primary->gpu->path) : ""))); drmFreeVersion(drmVer); listeners.gpuChange = gpu->events.change.registerListener([this](std::any d) { auto E = std::any_cast<CSessionDevice::SChangeEvent>(d); if (E.type == CSessionDevice::AQ_SESSION_EVENT_CHANGE_HOTPLUG) { backend->log(AQ_LOG_DEBUG, std::format("drm: Got a hotplug event for {}", gpuName)); scanConnectors(); recheckCRTCs(); } else if (E.type == CSessionDevice::AQ_SESSION_EVENT_CHANGE_LEASE) { backend->log(AQ_LOG_DEBUG, std::format("drm: Got a lease event for {}", gpuName)); scanLeases(); } }); listeners.gpuRemove = gpu->events.remove.registerListener( [this](std::any d) { backend->log(AQ_LOG_ERROR, std::format("drm: !!!!FIXME: Got a remove event for {}, this is not handled properly!!!!!", gpuName)); }); return true; } eBackendType Aquamarine::CDRMBackend::type() { return eBackendType::AQ_BACKEND_DRM; } void Aquamarine::CDRMBackend::scanConnectors() { backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connectors for {}", gpu->path)); auto resources = drmModeGetResources(gpu->fd); if (!resources) { backend->log(AQ_LOG_ERROR, std::format("drm: Scanning connectors for {} failed", gpu->path)); return; } for (size_t i = 0; i < resources->count_connectors; ++i) { uint32_t connectorID = resources->connectors[i]; SP<SDRMConnector> conn; auto drmConn = drmModeGetConnector(gpu->fd, connectorID); backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connector id {}", connectorID)); if (!drmConn) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to get connector id {}", connectorID)); continue; } auto it = std::find_if(connectors.begin(), connectors.end(), [connectorID](const auto& e) { return e->id == connectorID; }); if (it == connectors.end()) { backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing connector id {}", connectorID)); conn = connectors.emplace_back(SP<SDRMConnector>(new SDRMConnector())); conn->self = conn; conn->backend = self; conn->id = connectorID; if (!conn->init(drmConn)) { backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID)); connectors.pop_back(); continue; } } else { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector id {} already initialized", connectorID)); conn = *it; } conn->status = drmConn->connection; if (conn->crtc) conn->recheckCRTCProps(); backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state: {}", connectorID, (int)drmConn->connection)); if (conn->status == DRM_MODE_CONNECTED && !conn->output) { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connected", conn->szName)); conn->connect(drmConn); } else if (conn->status != DRM_MODE_CONNECTED && conn->output) { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} disconnected", conn->szName)); conn->disconnect(); } drmModeFreeConnector(drmConn); } drmModeFreeResources(resources); } void Aquamarine::CDRMBackend::scanLeases() { auto lessees = drmModeListLessees(gpu->fd); if (!lessees) { backend->log(AQ_LOG_ERROR, "drmModeListLessees failed"); return; } for (auto& c : connectors) { if (!c->output || !c->output->lease) continue; bool has = false; for (size_t i = 0; i < lessees->count; ++i) { if (lessees->lessees[i] == c->output->lease->lesseeID) { has = true; break; } } if (has) continue; backend->log(AQ_LOG_DEBUG, std::format("lessee {} gone, removing", c->output->lease->lesseeID)); // don't terminate c->output->lease->active = false; auto l = c->output->lease; for (auto& c2 : connectors) { if (!c2->output || c2->output->lease != c->output->lease) continue; c2->output->lease.reset(); } l->destroy(); } drmFree(lessees); } bool Aquamarine::CDRMBackend::start() { impl->reset(); return true; } std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> Aquamarine::CDRMBackend::pollFDs() { return {makeShared<SPollFD>(gpu->fd, [this]() { dispatchEvents(); })}; } int Aquamarine::CDRMBackend::drmFD() { return gpu->fd; } static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) { auto pageFlip = (SDRMPageFlip*)data; if (!pageFlip->connector) return; pageFlip->connector->isPageFlipPending = false; const auto& BACKEND = pageFlip->connector->backend; TRACE(BACKEND->log(AQ_LOG_TRACE, std::format("drm: pf event seq {} sec {} usec {} crtc {}", seq, tv_sec, tv_usec, crtc_id))); if (pageFlip->connector->status != DRM_MODE_CONNECTED || !pageFlip->connector->crtc) { BACKEND->log(AQ_LOG_DEBUG, "drm: Ignoring a pf event from a disabled crtc / connector"); return; } pageFlip->connector->onPresent(); uint32_t flags = IOutput::AQ_OUTPUT_PRESENT_VSYNC | IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK | IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION | IOutput::AQ_OUTPUT_PRESENT_ZEROCOPY; timespec presented = {.tv_sec = (time_t)tv_sec, .tv_nsec = (long)(tv_usec * 1000)}; pageFlip->connector->output->events.present.emit(IOutput::SPresentEvent{ .presented = BACKEND->sessionActive(), .when = &presented, .seq = seq, .refresh = (int)(pageFlip->connector->refresh ? (1000000000000LL / pageFlip->connector->refresh) : 0), .flags = flags, }); if (BACKEND->sessionActive() && !pageFlip->connector->frameEventScheduled && pageFlip->connector->output->enabledState) pageFlip->connector->output->events.frame.emit(); } bool Aquamarine::CDRMBackend::dispatchEvents() { drmEventContext event = { .version = 3, .page_flip_handler2 = ::handlePF, }; if (drmHandleEvent(gpu->fd, &event) != 0) backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd)); return true; } uint32_t Aquamarine::CDRMBackend::capabilities() { if (getCursorFormats().empty()) return 0; return eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER; } bool Aquamarine::CDRMBackend::setCursor(SP<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::CDRMBackend::onReady() { backend->log(AQ_LOG_DEBUG, std::format("drm: Connectors size2 {}", connectors.size())); // init a drm renderer to gather gl formats. // if we are secondary, initMgpu will have done that if (!primary) { auto a = CGBMAllocator::create(backend->reopenDRMNode(gpu->fd), backend); if (!a) backend->log(AQ_LOG_ERROR, "drm: onReady: no renderer for gl formats"); else { auto r = CDRMRenderer::attempt(a, backend.lock()); if (!r) backend->log(AQ_LOG_ERROR, "drm: onReady: no renderer for gl formats"); else { TRACE(backend->log(AQ_LOG_TRACE, std::format("drm: onReady: gathered {} gl formats", r->formats.size()))); buildGlFormats(r->formats); r.reset(); a.reset(); } } } for (auto& c : connectors) { backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {}", c->id)); if (!c->output) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {} has output name {}", c->id, c->output->name)); // swapchain has to be created here because allocator is absent in connect if not ready c->output->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); c->output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true, .multigpu = !!primary}); // mark the swapchain for scanout c->output->needsFrame = true; backend->events.newOutput.emit(SP<IOutput>(c->output)); } if (!initMgpu()) { backend->log(AQ_LOG_ERROR, "drm: Failed initializing mgpu"); return; } } std::vector<SDRMFormat> Aquamarine::CDRMBackend::getRenderFormats() { for (auto& p : planes) { if (p->type != DRM_PLANE_TYPE_PRIMARY) continue; return p->formats; } return {}; } std::vector<SDRMFormat> Aquamarine::CDRMBackend::getRenderableFormats() { return glFormats; } std::vector<SDRMFormat> Aquamarine::CDRMBackend::getCursorFormats() { for (auto& p : planes) { if (p->type != DRM_PLANE_TYPE_CURSOR) continue; if (primary) { TRACE(backend->log(AQ_LOG_TRACE, std::format("drm: getCursorFormats on secondary {}", gpu->path))); // this is a secondary GPU renderer. In order to receive buffers, // we'll force linear modifiers. // TODO: don't. Find a common maybe? auto fmts = p->formats; for (auto& fmt : fmts) { fmt.modifiers = {DRM_FORMAT_MOD_LINEAR}; } return fmts; } return p->formats; } return {}; } bool Aquamarine::CDRMBackend::createOutput(const std::string&) { return false; } int Aquamarine::CDRMBackend::getNonMasterFD() { int fd = open(gpuName.c_str(), O_RDWR | O_CLOEXEC); if (fd < 0) { backend->log(AQ_LOG_ERROR, "drm: couldn't dupe fd for non master"); return -1; } if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { backend->log(AQ_LOG_ERROR, "drm: couldn't drop master from duped fd"); return -1; } return fd; } SP<IAllocator> Aquamarine::CDRMBackend::preferredAllocator() { return backend->primaryAllocator; } bool Aquamarine::SDRMPlane::init(drmModePlane* plane) { id = plane->plane_id; if (!getDRMPlaneProps(backend->gpu->fd, id, &props)) return false; if (!getDRMProp(backend->gpu->fd, id, props.type, &type)) return false; initialID = id; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has type {}", id, (int)type)); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has {} formats", id, plane->count_formats)); for (size_t i = 0; i < plane->count_formats; ++i) { if (type != DRM_PLANE_TYPE_CURSOR) formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID}}); else formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR}}); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Format {}", fourccToName(plane->formats[i])))); } if (props.in_formats && backend->drmProps.supportsAddFb2Modifiers) { backend->backend->log(AQ_LOG_DEBUG, "drm: Plane: checking for modifiers"); uint64_t blobID = 0; if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID)) { backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No blob id"); return false; } auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID); if (!blob) { backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No property"); return false; } drmModeFormatModifierIterator iter = {0}; while (drmModeFormatModifierBlobIterNext(blob, &iter)) { auto it = std::find_if(formats.begin(), formats.end(), [iter](const auto& e) { return e.drmFormat == iter.fmt; }); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Modifier {} with format {}", iter.mod, fourccToName(iter.fmt)))); if (it == formats.end()) formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}}); else it->modifiers.emplace_back(iter.mod); } drmModeFreePropertyBlob(blob); } for (size_t i = 0; i < backend->crtcs.size(); ++i) { uint32_t crtcBit = (1 << i); if (!(plane->possible_crtcs & crtcBit)) continue; auto CRTC = backend->crtcs.at(i); if (type == DRM_PLANE_TYPE_PRIMARY && !CRTC->primary) { CRTC->primary = self.lock(); break; } if (type == DRM_PLANE_TYPE_CURSOR && !CRTC->cursor) { CRTC->cursor = self.lock(); break; } } return true; } SP<SDRMCRTC> Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* connector) { uint32_t crtcID = 0; if (props.crtc_id) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Using crtc_id for finding crtc")); uint64_t value = 0; if (!getDRMProp(backend->gpu->fd, id, props.crtc_id, &value)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to get CRTC_ID"); return nullptr; } crtcID = static_cast<uint32_t>(value); } else if (connector->encoder_id) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Using encoder_id for finding crtc")); auto encoder = drmModeGetEncoder(backend->gpu->fd, connector->encoder_id); if (!encoder) { backend->backend->log(AQ_LOG_ERROR, "drm: drmModeGetEncoder failed"); return nullptr; } crtcID = encoder->crtc_id; drmModeFreeEncoder(encoder); } else { backend->backend->log(AQ_LOG_ERROR, "drm: Connector has neither crtc_id nor encoder_id"); return nullptr; } if (crtcID == 0) { backend->backend->log(AQ_LOG_ERROR, "drm: getCurrentCRTC: No CRTC 0"); return nullptr; } auto it = std::find_if(backend->crtcs.begin(), backend->crtcs.end(), [crtcID](const auto& e) { return e->id == crtcID; }); if (it == backend->crtcs.end()) { backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to find a CRTC with ID {}", crtcID)); return nullptr; } return *it; } bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) { pendingPageFlip.connector = self.lock(); if (!getDRMConnectorProps(backend->gpu->fd, id, &props)) return false; auto name = drmModeGetConnectorTypeName(connector->connector_type); if (!name) name = "ERROR"; szName = std::format("{}-{}", name, connector->connector_type_id); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connector gets name {}", szName)); possibleCrtcs = drmModeConnectorGetPossibleCrtcs(backend->gpu->fd, connector); if (!possibleCrtcs) backend->backend->log(AQ_LOG_ERROR, "drm: No CRTCs possible"); crtc = getCurrentCRTC(connector); return true; } Aquamarine::SDRMConnector::~SDRMConnector() { disconnect(); } static int32_t calculateRefresh(const drmModeModeInfo& mode) { int32_t refresh = (mode.clock * 1000000LL / mode.htotal + mode.vtotal / 2) / mode.vtotal; if (mode.flags & DRM_MODE_FLAG_INTERLACE) refresh *= 2; if (mode.flags & DRM_MODE_FLAG_DBLSCAN) refresh /= 2; if (mode.vscan > 1) refresh /= mode.vscan; return refresh; } drmModeModeInfo* Aquamarine::SDRMConnector::getCurrentMode() { if (!crtc) return nullptr; if (crtc->props.mode_id) { size_t size = 0; return (drmModeModeInfo*)getDRMPropBlob(backend->gpu->fd, crtc->id, crtc->props.mode_id, &size); } auto drmCrtc = drmModeGetCrtc(backend->gpu->fd, crtc->id); if (!drmCrtc) return nullptr; if (!drmCrtc->mode_valid) { drmModeFreeCrtc(drmCrtc); return nullptr; } drmModeModeInfo* modeInfo = (drmModeModeInfo*)malloc(sizeof(drmModeModeInfo)); if (!modeInfo) { drmModeFreeCrtc(drmCrtc); return nullptr; } *modeInfo = drmCrtc->mode; drmModeFreeCrtc(drmCrtc); return modeInfo; } void Aquamarine::SDRMConnector::parseEDID(std::vector<uint8_t> data) { auto info = di_info_parse_edid(data.data(), data.size()); if (!info) { backend->backend->log(AQ_LOG_ERROR, "drm: failed to parse edid"); return; } auto edid = di_info_get_edid(info); auto venProduct = di_edid_get_vendor_product(edid); auto pnpID = std::string{venProduct->manufacturer, 3}; if (PNPIDS.contains(pnpID)) make = PNPIDS.at(pnpID); else make = pnpID; auto mod = di_info_get_model(info); auto ser = di_info_get_serial(info); model = mod ? mod : ""; serial = ser ? ser : ""; di_info_destroy(info); } void Aquamarine::SDRMConnector::recheckCRTCProps() { if (!crtc || !output) return; uint64_t prop = 0; canDoVrr = props.vrr_capable && crtc->props.vrr_enabled && getDRMProp(backend->gpu->fd, id, props.vrr_capable, &prop) && prop; output->vrrCapable = canDoVrr; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: connector {} crtc is {} of vrr: props.vrr_capable -> {}, crtc->props.vrr_enabled -> {}", szName, (canDoVrr ? "capable" : "incapable"), props.vrr_capable, crtc->props.vrr_enabled)); output->supportsExplicit = backend->drmProps.supportsTimelines && crtc->props.out_fence_ptr && crtc->primary->props.in_fence_fd; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit sync {}", output->supportsExplicit ? "supported" : "unsupported")); } void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { if (output) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not connecting connector {} because it's already connected", szName)); return; } backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connecting connector {}, {}", szName, crtc ? std::format("CRTC ID {}", crtc->id) : "no CRTC")); output = SP<CDRMOutput>(new CDRMOutput(szName, backend, self.lock())); output->self = output; output->connector = self.lock(); backend->backend->log(AQ_LOG_DEBUG, "drm: Dumping detected modes:"); auto currentModeInfo = getCurrentMode(); for (int i = 0; i < connector->count_modes; ++i) { auto& drmMode = connector->modes[i]; if (drmMode.flags & DRM_MODE_FLAG_INTERLACE) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping mode {} because it's interlaced", i)); continue; } auto aqMode = makeShared<SOutputMode>(); aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay}; aqMode->refreshRate = calculateRefresh(drmMode); aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED); aqMode->modeInfo = drmMode; if (i == 1) fallbackMode = aqMode; output->modes.emplace_back(aqMode); if (currentModeInfo && std::memcmp(&drmMode, currentModeInfo, sizeof(drmModeModeInfo))) { output->state->setMode(aqMode); //uint64_t modeID = 0; // getDRMProp(backend->gpu->fd, crtc->id, crtc->props.mode_id, &modeID); } backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Mode {}: {}x{}@{:.2f}Hz {}", i, (int)aqMode->pixelSize.x, (int)aqMode->pixelSize.y, aqMode->refreshRate / 1000.0, aqMode->preferred ? " (preferred)" : "")); } if (!currentModeInfo && fallbackMode) output->state->setMode(fallbackMode); output->physicalSize = {(double)connector->mmWidth, (double)connector->mmHeight}; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Physical size {} (mm)", output->physicalSize)); switch (connector->subpixel) { case DRM_MODE_SUBPIXEL_NONE: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_NONE; break; case DRM_MODE_SUBPIXEL_UNKNOWN: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_RGB; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_BGR; break; case DRM_MODE_SUBPIXEL_VERTICAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_RGB; break; case DRM_MODE_SUBPIXEL_VERTICAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_BGR; break; default: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; } uint64_t prop = 0; if (getDRMProp(backend->gpu->fd, id, props.non_desktop, &prop)) { if (prop == 1) backend->backend->log(AQ_LOG_DEBUG, "drm: Non-desktop connector"); output->nonDesktop = prop; } maxBpcBounds.fill(0); if (props.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1])) backend->backend->log(AQ_LOG_ERROR, "drm: Failed to check max_bpc"); size_t edidLen = 0; uint8_t* edidData = (uint8_t*)getDRMPropBlob(backend->gpu->fd, id, props.edid, &edidLen); std::vector<uint8_t> edid{edidData, edidData + edidLen}; parseEDID(edid); free(edidData); edid = {}; // TODO: subconnectors output->make = make; output->model = model; output->serial = serial; output->description = std::format("{} {} {} ({})", make, model, serial, szName); output->needsFrame = true; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description)); status = DRM_MODE_CONNECTED; recheckCRTCProps(); if (!backend->backend->ready) return; output->swapchain = CSwapchain::create(backend->backend->primaryAllocator, backend->self.lock()); backend->backend->events.newOutput.emit(SP<IOutput>(output)); output->scheduleFrame(IOutput::AQ_SCHEDULE_NEW_CONNECTOR); } void Aquamarine::SDRMConnector::disconnect() { if (!output) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName)); return; } output->events.destroy.emit(); output.reset(); status = DRM_MODE_DISCONNECTED; } bool Aquamarine::SDRMConnector::commitState(SDRMConnectorCommitData& data) { const bool ok = backend->impl->commit(self.lock(), data); if (ok && !data.test) applyCommit(data); else rollbackCommit(data); return ok; } void Aquamarine::SDRMConnector::applyCommit(const SDRMConnectorCommitData& data) { crtc->primary->back = data.mainFB; if (crtc->cursor && data.cursorFB) crtc->cursor->back = data.cursorFB; if (data.mainFB) data.mainFB->buffer->lockedByBackend = true; if (crtc->cursor && data.cursorFB) data.cursorFB->buffer->lockedByBackend = true; pendingCursorFB.reset(); if (output->state->state().committed & COutputState::AQ_OUTPUT_STATE_MODE) refresh = calculateRefresh(data.modeInfo); output->enabledState = output->state->state().enabled; } void Aquamarine::SDRMConnector::rollbackCommit(const SDRMConnectorCommitData& data) { // cursors are applied regardless, // unless this was a test if (data.test) return; if (crtc->cursor && data.cursorFB) crtc->cursor->back = data.cursorFB; crtc->pendingCursor.reset(); } void Aquamarine::SDRMConnector::onPresent() { crtc->primary->last = crtc->primary->front; crtc->primary->front = crtc->primary->back; if (crtc->primary->last && crtc->primary->last->buffer) { crtc->primary->last->buffer->lockedByBackend = false; crtc->primary->last->buffer->events.backendRelease.emit(); } if (crtc->cursor) { crtc->cursor->last = crtc->cursor->front; crtc->cursor->front = crtc->cursor->back; if (crtc->cursor->last && crtc->cursor->last->buffer) { crtc->cursor->last->buffer->lockedByBackend = false; crtc->cursor->last->buffer->events.backendRelease.emit(); } } } Aquamarine::CDRMOutput::~CDRMOutput() { backend->backend->removeIdleEvent(frameIdle); connector->isPageFlipPending = false; connector->frameEventScheduled = false; } bool Aquamarine::CDRMOutput::commit() { return commitState(); } bool Aquamarine::CDRMOutput::test() { return commitState(true); } void Aquamarine::CDRMOutput::setCursorVisible(bool visible) { cursorVisible = visible; scheduleFrame(AQ_SCHEDULE_CURSOR_VISIBLE); } bool Aquamarine::CDRMOutput::commitState(bool onlyTest) { if (!backend->backend->session->active) { backend->backend->log(AQ_LOG_ERROR, "drm: Session inactive"); return false; } if (!connector->crtc) { backend->backend->log(AQ_LOG_ERROR, "drm: No CRTC attached to output"); return false; } const auto& STATE = state->state(); const uint32_t COMMITTED = STATE.committed; if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED) && STATE.enabled) { if (!STATE.mode && !STATE.customMode) { backend->backend->log(AQ_LOG_ERROR, "drm: No mode on enable commit"); return false; } } if (STATE.adaptiveSync && !connector->canDoVrr) { backend->backend->log(AQ_LOG_ERROR, "drm: No Adaptive sync support for output"); return false; } if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && !backend->drmProps.supportsAsyncCommit) { backend->backend->log(AQ_LOG_ERROR, "drm: No Immediate presentation support in the backend"); return false; } if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && !STATE.buffer) { backend->backend->log(AQ_LOG_ERROR, "drm: No buffer committed"); return false; } if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && STATE.buffer->attachments.has(AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE)) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Cannot commit a KMS-unimportable buffer.")); return false; } // If we are changing the rendering format, we may need to reconfigure the output (aka modeset) // which may result in some glitches const bool NEEDS_RECONFIG = COMMITTED & (COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_FORMAT | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_MODE); const bool BLOCKING = NEEDS_RECONFIG || !(COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER); const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; if (!MODE) // modeless commits are invalid return false; uint32_t flags = 0; if (!onlyTest) { if (NEEDS_RECONFIG) { if (STATE.enabled) backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Modesetting {} with {}x{}@{:.2f}Hz", name, (int)MODE->pixelSize.x, (int)MODE->pixelSize.y, MODE->refreshRate / 1000.F)); else backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Disabling output {}", name)); } if ((NEEDS_RECONFIG || (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) && connector->isPageFlipPending) { backend->backend->log(AQ_LOG_ERROR, "drm: Cannot commit when a page-flip is awaiting"); return false; } if (STATE.enabled && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) flags |= DRM_MODE_PAGE_FLIP_EVENT; if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) { flags |= DRM_MODE_PAGE_FLIP_ASYNC; flags &= ~DRM_MODE_PAGE_FLIP_EVENT; // Do not request an event for immediate page flips, as it makes no sense. } } // we can't go further without a blit if (backend->primary && onlyTest) return true; SDRMConnectorCommitData data; if (STATE.buffer) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state")); SP<CDRMFB> drmFB; if (backend->shouldBlit()) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires blit, blitting")); if (!mgpu.swapchain) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No swapchain for blit, creating")); mgpu.swapchain = CSwapchain::create(backend->rendererState.allocator, backend.lock()); } auto OPTIONS = swapchain->currentOptions(); auto bufDma = STATE.buffer->dmabuf(); OPTIONS.size = STATE.buffer->size; if (OPTIONS.format == DRM_FORMAT_INVALID) OPTIONS.format = bufDma.format; OPTIONS.multigpu = false; // this is not a shared swapchain, and additionally, don't make it linear, nvidia would be mad OPTIONS.cursor = false; OPTIONS.scanout = true; if (!mgpu.swapchain->reconfigure(OPTIONS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain failed reconfiguring"); return false; } auto NEWAQBUF = mgpu.swapchain->next(nullptr); auto blitResult = backend->rendererState.renderer->blit( STATE.buffer, NEWAQBUF, (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) ? STATE.explicitInFence : -1); if (!blitResult.success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but blit failed"); return false; } // replace the explicit in fence if the blitting backend returned one, otherwise discard old. Passed fence from the client is wrong. // if the commit doesn't have an explicit fence, don't use the one we created, just fallback to implicit static auto NO_EXPLICIT = envEnabled("AQ_MGPU_NO_EXPLICIT"); if (blitResult.syncFD.has_value() && !NO_EXPLICIT && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE)) state->setExplicitInFence(blitResult.syncFD.value()); else state->setExplicitInFence(-1); drmFB = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present } else drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); // will return attachment if present if (!drmFB) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); return false; } if (drmFB->dead) { backend->backend->log(AQ_LOG_ERROR, "drm: KMS buffer is dead?!"); return false; } data.mainFB = drmFB; } // sometimes, our consumer could f up the swapchain format and change it without the state changing bool formatMismatch = false; if (data.mainFB) { if (const auto params = data.mainFB->buffer->dmabuf(); params.success && params.format != STATE.drmFormat) { // formats mismatch. Update the state format and roll with it backend->backend->log(AQ_LOG_WARNING, std::format("drm: Formats mismatch in commit, buffer is {} but output is set to {}. Modesetting to {}", fourccToName(params.format), fourccToName(STATE.drmFormat), fourccToName(params.format))); state->setFormat(params.format); formatMismatch = true; // TODO: reject if tearing? We will miss a frame event! flags &= ~DRM_MODE_PAGE_FLIP_ASYNC; // we cannot modeset with async pf } } if (connector->crtc->pendingCursor) data.cursorFB = connector->crtc->pendingCursor; else if (connector->crtc->cursor) data.cursorFB = connector->crtc->cursor->front; if (data.cursorFB) { // verify cursor format. This might be wrong on NVIDIA where linear buffers // fail to be created from gbm // TODO: add an API to detect this and request drm_dumb linear buffers. Or do something, // idk if (data.cursorFB->dead || data.cursorFB->buffer->dmabuf().modifier == DRM_FORMAT_MOD_INVALID) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Dropping invalid buffer for cursor plane")); data.cursorFB = nullptr; } } data.blocking = BLOCKING || formatMismatch; data.modeset = NEEDS_RECONFIG || lastCommitNoBuffer || formatMismatch; data.flags = flags; data.test = onlyTest; if (MODE->modeInfo.has_value()) data.modeInfo = *MODE->modeInfo; else data.calculateMode(connector); bool ok = connector->commitState(data); if (!ok && !data.modeset && !connector->commitTainted) { // attempt to re-modeset, however, flip a tainted flag if the modesetting fails // to avoid doing this over and over. data.modeset = true; data.blocking = true; data.flags = onlyTest ? 0 : DRM_MODE_PAGE_FLIP_EVENT; ok = connector->commitState(data); if (!ok) connector->commitTainted = true; } if (onlyTest || !ok) return ok; events.commit.emit(); state->onCommit(); lastCommitNoBuffer = !data.mainFB; needsFrame = false; if (ok) connector->commitTainted = false; if (data.flags & DRM_MODE_PAGE_FLIP_ASYNC) { // for tearing commits, we will send presentation feedback instantly, and rotate // drm framebuffers to properly send backendRelease events. // the last FB should already be gone from KMS because it's been immediately replaced // no completion and no vsync, because tearing uint32_t flags = IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK | IOutput::AQ_OUTPUT_PRESENT_ZEROCOPY; timespec presented; clock_gettime(CLOCK_MONOTONIC, &presented); connector->output->events.present.emit(IOutput::SPresentEvent{ .presented = backend->sessionActive(), .when = &presented, .seq = 0, /* unknown sequence for tearing */ .refresh = (int)(connector->refresh ? (1000000000000LL / connector->refresh) : 0), .flags = flags, }); connector->onPresent(); } return ok; } SP<IBackendImplementation> Aquamarine::CDRMOutput::getBackend() { return backend.lock(); } bool Aquamarine::CDRMOutput::setCursor(SP<IBuffer> buffer, const Vector2D& hotspot) { if (buffer && !buffer->dmabuf().success) { backend->backend->log(AQ_LOG_ERROR, "drm: Cursor buffer has to be a dmabuf"); return false; } if (!buffer) setCursorVisible(false); else { SP<CDRMFB> fb; if (backend->primary) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires cursor blit, blitting")); if (!mgpu.cursorSwapchain) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No cursorSwapchain for blit, creating")); mgpu.cursorSwapchain = CSwapchain::create(backend->rendererState.allocator, backend.lock()); } auto OPTIONS = mgpu.cursorSwapchain->currentOptions(); OPTIONS.multigpu = false; OPTIONS.scanout = true; OPTIONS.cursor = true; OPTIONS.format = buffer->dmabuf().format; OPTIONS.size = buffer->dmabuf().size; OPTIONS.length = 2; if (!mgpu.cursorSwapchain->reconfigure(OPTIONS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu cursorSwapchain failed reconfiguring"); return false; } auto NEWAQBUF = mgpu.cursorSwapchain->next(nullptr); if (!backend->rendererState.renderer->blit(buffer, NEWAQBUF).success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but cursor blit failed"); return false; } fb = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present } else fb = CDRMFB::create(buffer, backend, nullptr); if (!fb) { backend->backend->log(AQ_LOG_ERROR, "drm: Cursor buffer failed to import to KMS"); return false; } cursorHotspot = hotspot; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Cursor buffer imported into KMS with id {}", fb->id)); connector->crtc->pendingCursor = fb; cursorVisible = true; } scheduleFrame(AQ_SCHEDULE_CURSOR_SHAPE); return true; } void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord, bool skipShedule) { cursorPos = coord; // cursorVisible = true; backend->impl->moveCursor(connector, skipShedule); } void Aquamarine::CDRMOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CDRMOutput::scheduleFrame: reason {}, needsFrame {}, isPageFlipPending {}, frameEventScheduled {}", (uint32_t)reason, needsFrame, connector->isPageFlipPending, connector->frameEventScheduled))); needsFrame = true; if (connector->isPageFlipPending || connector->frameEventScheduled || !enabledState) return; connector->frameEventScheduled = true; backend->backend->addIdleEvent(frameIdle); } Vector2D Aquamarine::CDRMOutput::cursorPlaneSize() { return backend->drmProps.cursorSize; } size_t Aquamarine::CDRMOutput::getGammaSize() { if (!backend->atomic) { backend->log(AQ_LOG_ERROR, "No support for gamma on the legacy iface"); return 0; } uint64_t size = 0; if (!getDRMProp(backend->gpu->fd, connector->crtc->id, connector->crtc->props.gamma_lut_size, &size)) { backend->log(AQ_LOG_ERROR, "Couldn't get the gamma_size prop"); return 0; } return size; } std::vector<SDRMFormat> Aquamarine::CDRMOutput::getRenderFormats() { return connector->crtc->primary->formats; } int Aquamarine::CDRMOutput::getConnectorID() { return connector->id; } Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, SP<SDRMConnector> connector_) : backend(backend_), connector(connector_) { name = name_; frameIdle = makeShared<std::function<void(void)>>([this]() { connector->frameEventScheduled = false; if (connector->isPageFlipPending) return; events.frame.emit(); }); } SP<CDRMFB> Aquamarine::CDRMFB::create(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, bool* isNew) { SP<CDRMFB> fb; if (isNew) *isNew = true; if (buffer_->attachments.has(AQ_ATTACHMENT_DRM_BUFFER)) { auto at = (CDRMBufferAttachment*)buffer_->attachments.get(AQ_ATTACHMENT_DRM_BUFFER).get(); fb = at->fb; TRACE(backend_->log(AQ_LOG_TRACE, std::format("drm: CDRMFB: buffer has drmfb attachment with fb {:x}", (uintptr_t)fb.get()))); } if (fb) { if (isNew) *isNew = false; return fb; } fb = SP<CDRMFB>(new CDRMFB(buffer_, backend_)); if (!fb->id) return nullptr; buffer_->attachments.add(makeShared<CDRMBufferAttachment>(fb)); return fb; } Aquamarine::CDRMFB::CDRMFB(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_) : buffer(buffer_), backend(backend_) { import(); } void Aquamarine::CDRMFB::import() { auto attrs = buffer->dmabuf(); if (!attrs.success) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted has no dmabuf"); return; } if (buffer->attachments.has(AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE)) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted is unimportable"); return; } // TODO: check format for (int i = 0; i < attrs.planes; ++i) { int ret = drmPrimeFDToHandle(backend->gpu->fd, attrs.fds.at(i), &boHandles[i]); if (ret) { backend->backend->log(AQ_LOG_ERROR, "drm: drmPrimeFDToHandle failed"); drop(); return; } TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: CDRMFB: plane {} has fd {}, got handle {}", i, attrs.fds.at(i), boHandles.at(i)))); } id = submitBuffer(); if (!id) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer to KMS"); buffer->attachments.add(makeShared<CDRMBufferUnimportable>()); drop(); return; } TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: new buffer {}", id))); // FIXME: why does this implode when it doesnt on wlroots or kwin? closeHandles(); listeners.destroyBuffer = buffer->events.destroy.registerListener([this](std::any d) { drop(); dead = true; id = 0; boHandles = {0, 0, 0, 0}; }); } void Aquamarine::CDRMFB::reimport() { drop(); dropped = false; handlesClosed = false; boHandles = {0, 0, 0, 0}; import(); } Aquamarine::CDRMFB::~CDRMFB() { drop(); } void Aquamarine::CDRMFB::closeHandles() { if (handlesClosed) return; handlesClosed = true; std::vector<uint32_t> closed; for (size_t i = 0; i < 4; ++i) { if (boHandles.at(i) == 0) continue; bool exists = false; for (size_t j = 0; j < i; ++j) { if (boHandles.at(i) == boHandles.at(j)) { exists = true; break; } } if (exists) continue; if (drmCloseBufferHandle(backend->gpu->fd, boHandles.at(i))) backend->backend->log(AQ_LOG_ERROR, "drm: drmCloseBufferHandle failed"); } boHandles = {0, 0, 0, 0}; } void Aquamarine::CDRMFB::drop() { if (dropped) return; dropped = true; if (!id) return; closeHandles(); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: dropping buffer {}", id))); int ret = drmModeCloseFB(backend->gpu->fd, id); if (ret == -EINVAL) ret = drmModeRmFB(backend->gpu->fd, id); if (ret) backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to close a buffer: {}", strerror(-ret))); } uint32_t Aquamarine::CDRMFB::submitBuffer() { auto attrs = buffer->dmabuf(); uint32_t newID = 0; std::array<uint64_t, 4> mods = {0, 0, 0, 0}; for (size_t i = 0; i < attrs.planes; ++i) { mods[i] = attrs.modifier; } if (backend->drmProps.supportsAddFb2Modifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: Using drmModeAddFB2WithModifiers to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, fourccToName(attrs.format), attrs.modifier))); if (drmModeAddFB2WithModifiers(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), mods.data(), &newID, DRM_MODE_FB_MODIFIERS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer with drmModeAddFB2WithModifiers"); return 0; } } else { if (attrs.modifier != DRM_FORMAT_MOD_INVALID && attrs.modifier != DRM_FORMAT_MOD_LINEAR) { backend->backend->log(AQ_LOG_ERROR, "drm: drmModeAddFB2WithModifiers unsupported and buffer has explicit modifiers"); return 0; } TRACE(backend->backend->log( AQ_LOG_TRACE, std::format("drm: Using drmModeAddFB2 to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, fourccToName(attrs.format), attrs.modifier))); if (drmModeAddFB2(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), &newID, 0)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer with drmModeAddFB2"); return 0; } } return newID; } void Aquamarine::SDRMConnectorCommitData::calculateMode(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector) { if (!connector || !connector->output || !connector->output->state) return; const auto& STATE = connector->output->state->state(); const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; if (!MODE) { connector->backend->log(AQ_LOG_ERROR, "drm: no mode in calculateMode??"); return; } di_cvt_options options = { .red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE, .h_pixels = (int)MODE->pixelSize.x, .v_lines = (int)MODE->pixelSize.y, .ip_freq_rqd = MODE->refreshRate ? MODE->refreshRate / 1000.0 : 60.0, }; di_cvt_timing timing; di_cvt_compute(&timing, &options); uint16_t hsync_start = (int)MODE->pixelSize.x + timing.h_front_porch; uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch; uint16_t hsync_end = hsync_start + timing.h_sync; uint16_t vsync_end = vsync_start + timing.v_sync; modeInfo = (drmModeModeInfo){ .clock = (uint32_t)std::round(timing.act_pixel_freq * 1000), .hdisplay = (uint16_t)MODE->pixelSize.x, .hsync_start = hsync_start, .hsync_end = hsync_end, .htotal = (uint16_t)(hsync_end + timing.h_back_porch), .vdisplay = (uint16_t)timing.v_lines_rnd, .vsync_start = vsync_start, .vsync_end = vsync_end, .vtotal = (uint16_t)(vsync_end + timing.v_back_porch), .vrefresh = (uint32_t)std::round(timing.act_frame_rate), .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, }; snprintf(modeInfo.name, sizeof(modeInfo.name), "%dx%d", (int)MODE->pixelSize.x, (int)MODE->pixelSize.y); TRACE(connector->backend->log(AQ_LOG_TRACE, std::format("drm: calculateMode: modeline dump: {} {} {} {} {} {} {} {} {} {} {}", modeInfo.clock, modeInfo.hdisplay, modeInfo.hsync_start, modeInfo.hsync_end, modeInfo.htotal, modeInfo.vdisplay, modeInfo.vsync_start, modeInfo.vsync_end, modeInfo.vtotal, modeInfo.vrefresh, modeInfo.flags))); } Aquamarine::CDRMBufferAttachment::CDRMBufferAttachment(SP<CDRMFB> fb_) : fb(fb_) { ; } SP<CDRMLease> Aquamarine::CDRMLease::create(std::vector<SP<IOutput>> outputs) { if (outputs.empty()) return nullptr; if (outputs.at(0)->getBackend()->type() != AQ_BACKEND_DRM) return nullptr; auto backend = ((CDRMBackend*)outputs.at(0)->getBackend().get())->self.lock(); for (auto& o : outputs) { if (o->getBackend() != backend) { backend->log(AQ_LOG_ERROR, "drm lease: Mismatched backends"); return nullptr; } } std::vector<uint32_t> objects; auto lease = SP<CDRMLease>(new CDRMLease); for (auto& o : outputs) { auto drmo = ((CDRMOutput*)o.get())->self.lock(); backend->log(AQ_LOG_DEBUG, std::format("drm lease: output {}, connector {}", drmo->name, drmo->connector->id)); // FIXME: do we have to alloc a crtc here? if (!drmo->connector->crtc) { backend->log(AQ_LOG_ERROR, std::format("drm lease: output {} has no crtc", drmo->name)); return nullptr; } backend->log(AQ_LOG_DEBUG, std::format("drm lease: crtc {}, primary {}", drmo->connector->crtc->id, drmo->connector->crtc->primary->id)); objects.push_back(drmo->connector->id); objects.push_back(drmo->connector->crtc->id); objects.push_back(drmo->connector->crtc->primary->id); if (drmo->connector->crtc->cursor) objects.push_back(drmo->connector->crtc->cursor->id); lease->outputs.emplace_back(drmo); } backend->log(AQ_LOG_DEBUG, "drm lease: issuing a lease"); int leaseFD = drmModeCreateLease(backend->gpu->fd, objects.data(), objects.size(), O_CLOEXEC, &lease->lesseeID); if (leaseFD < 0) { backend->log(AQ_LOG_ERROR, "drm lease: drm rejected a lease"); return nullptr; } for (auto& o : lease->outputs) { o->lease = lease; } lease->leaseFD = leaseFD; backend->log(AQ_LOG_DEBUG, std::format("drm lease: lease granted with lessee id {}", lease->lesseeID)); return lease; } Aquamarine::CDRMLease::~CDRMLease() { if (active) terminate(); else destroy(); } void Aquamarine::CDRMLease::terminate() { active = false; if (drmModeRevokeLease(backend->gpu->fd, lesseeID) < 0) backend->log(AQ_LOG_ERROR, "drm lease: Failed to revoke lease"); destroy(); } void Aquamarine::CDRMLease::destroy() { events.destroy.emit(); } 07070100000038000081A400000000000000000000000166C38D3F00001241000000000000000000000000000000000000002A00000000aquamarine-0.3.3/src/backend/drm/Math.cpp#include "Math.hpp" #include <unordered_map> #include <cstring> #include <cmath> void matrixIdentity(float mat[9]) { static const float identity[9] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; memcpy(mat, identity, sizeof(identity)); } void matrixMultiply(float mat[9], const float a[9], const float b[9]) { float product[9]; product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]; product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]; product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]; product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]; product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]; product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]; product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]; product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; memcpy(mat, product, sizeof(product)); } void matrixTranspose(float mat[9], const float a[9]) { float transposition[9] = { a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8], }; memcpy(mat, transposition, sizeof(transposition)); } void matrixTranslate(float mat[9], float x, float y) { float translate[9] = { 1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, translate); } void matrixScale(float mat[9], float x, float y) { float scale[9] = { x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, scale); } void matrixRotate(float mat[9], float rad) { float rotate[9] = { (float)cos(rad), (float)-sin(rad), 0.0f, (float)sin(rad), (float)cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, rotate); } std::unordered_map<eTransform, std::array<float, 9>> transforms = { {HYPRUTILS_TRANSFORM_NORMAL, { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_90, { 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_180, { -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_270, { 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED, { -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_90, { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_180, { 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_270, { 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, }; void matrixTransform(float mat[9], eTransform transform) { matrixMultiply(mat, mat, transforms.at(transform).data()); } void matrixProjection(float mat[9], int width, int height, eTransform transform) { memset(mat, 0, sizeof(*mat) * 9); const float* t = transforms.at(transform).data(); float x = 2.0f / width; float y = 2.0f / height; // Rotation + reflection mat[0] = x * t[0]; mat[1] = x * t[1]; mat[3] = y * -t[3]; mat[4] = y * -t[4]; // Translation mat[2] = -copysign(1.0f, mat[0] + mat[1]); mat[5] = -copysign(1.0f, mat[3] + mat[4]); // Identity mat[8] = 1.0f; } void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]) { double x = box.x; double y = box.y; double width = box.width; double height = box.height; matrixIdentity(mat); matrixTranslate(mat, x, y); if (rotation != 0) { matrixTranslate(mat, width / 2, height / 2); matrixRotate(mat, rotation); matrixTranslate(mat, -width / 2, -height / 2); } matrixScale(mat, width, height); if (transform != HYPRUTILS_TRANSFORM_NORMAL) { matrixTranslate(mat, 0.5, 0.5); matrixTransform(mat, transform); matrixTranslate(mat, -0.5, -0.5); } matrixMultiply(mat, projection, mat); } 07070100000039000081A400000000000000000000000166C38D3F000002C5000000000000000000000000000000000000002A00000000aquamarine-0.3.3/src/backend/drm/Math.hpp#pragma once // FIXME: migrate this to utils // includes box and vector as well #include <hyprutils/math/Region.hpp> using namespace Hyprutils::Math; void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]); void matrixProjection(float mat[9], int width, int height, eTransform transform); void matrixTransform(float mat[9], eTransform transform); void matrixRotate(float mat[9], float rad); void matrixScale(float mat[9], float x, float y); void matrixTranslate(float mat[9], float x, float y); void matrixTranspose(float mat[9], const float a[9]); void matrixMultiply(float mat[9], const float a[9], const float b[9]); void matrixIdentity(float mat[9]); 0707010000003A000081A400000000000000000000000166C38D3F0000175C000000000000000000000000000000000000002B00000000aquamarine-0.3.3/src/backend/drm/Props.cpp#include <aquamarine/backend/DRM.hpp> extern "C" { #include <libseat.h> #include <libudev.h> #include <xf86drm.h> #include <xf86drmMode.h> } #include <cstring> using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer struct prop_info { const char* name; size_t index; }; static const struct prop_info connector_info[] = { #define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorProps, name) / sizeof(uint32_t)) {"CRTC_ID", INDEX(crtc_id)}, {"DPMS", INDEX(dpms)}, {"EDID", INDEX(edid)}, {"PATH", INDEX(path)}, {"content type", INDEX(content_type)}, {"link-status", INDEX(link_status)}, {"max bpc", INDEX(max_bpc)}, {"non-desktop", INDEX(non_desktop)}, {"panel orientation", INDEX(panel_orientation)}, {"subconnector", INDEX(subconnector)}, {"vrr_capable", INDEX(vrr_capable)}, #undef INDEX }; static const struct prop_info crtc_info[] = { #define INDEX(name) (offsetof(SDRMCRTC::UDRMCRTCProps, name) / sizeof(uint32_t)) {"ACTIVE", INDEX(active)}, {"GAMMA_LUT", INDEX(gamma_lut)}, {"GAMMA_LUT_SIZE", INDEX(gamma_lut_size)}, {"MODE_ID", INDEX(mode_id)}, {"OUT_FENCE_PTR", INDEX(out_fence_ptr)}, {"VRR_ENABLED", INDEX(vrr_enabled)}, #undef INDEX }; static const struct prop_info plane_info[] = { #define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, name) / sizeof(uint32_t)) {"CRTC_H", INDEX(crtc_h)}, {"CRTC_ID", INDEX(crtc_id)}, {"CRTC_W", INDEX(crtc_w)}, {"CRTC_X", INDEX(crtc_x)}, {"CRTC_Y", INDEX(crtc_y)}, {"FB_DAMAGE_CLIPS", INDEX(fb_damage_clips)}, {"FB_ID", INDEX(fb_id)}, {"HOTSPOT_X", INDEX(hotspot_x)}, {"HOTSPOT_Y", INDEX(hotspot_y)}, {"IN_FENCE_FD", INDEX(in_fence_fd)}, {"IN_FORMATS", INDEX(in_formats)}, {"SRC_H", INDEX(src_h)}, {"SRC_W", INDEX(src_w)}, {"SRC_X", INDEX(src_x)}, {"SRC_Y", INDEX(src_y)}, {"rotation", INDEX(rotation)}, {"type", INDEX(type)}, #undef INDEX }; namespace Aquamarine { static int comparePropInfo(const void* arg1, const void* arg2) { const char* key = (const char*)arg1; const prop_info* elem = (prop_info*)arg2; return strcmp(key, elem->name); } static bool scanProperties(int fd, uint32_t id, uint32_t type, uint32_t* result, const prop_info* info, size_t info_len) { drmModeObjectProperties* props = drmModeObjectGetProperties(fd, id, type); if (!props) return false; for (uint32_t i = 0; i < props->count_props; ++i) { drmModePropertyRes* prop = drmModeGetProperty(fd, props->props[i]); if (!prop) continue; const prop_info* p = (prop_info*)bsearch(prop->name, info, info_len, sizeof(info[0]), comparePropInfo); if (p) result[p->index] = prop->prop_id; drmModeFreeProperty(prop); } drmModeFreeObjectProperties(props); return true; } bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, connector_info, sizeof(connector_info) / sizeof(connector_info[0])); } bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); } bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0])); } bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) { drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); if (!props) return false; bool found = false; for (uint32_t i = 0; i < props->count_props; ++i) { if (props->props[i] == prop) { *ret = props->prop_values[i]; found = true; break; } } drmModeFreeObjectProperties(props); return found; } void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len) { uint64_t blob_id; if (!getDRMProp(fd, obj, prop, &blob_id)) return nullptr; drmModePropertyBlobRes* blob = drmModeGetPropertyBlob(fd, blob_id); if (!blob) return nullptr; void* ptr = malloc(blob->length); if (!ptr) { drmModeFreePropertyBlob(blob); return nullptr; } memcpy(ptr, blob->data, blob->length); *ret_len = blob->length; drmModeFreePropertyBlob(blob); return ptr; } char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id) { uint64_t value; if (!getDRMProp(fd, obj, prop_id, &value)) return nullptr; drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); if (!prop) return nullptr; char* str = nullptr; for (int i = 0; i < prop->count_enums; i++) { if (prop->enums[i].value == value) { str = strdup(prop->enums[i].name); break; } } drmModeFreeProperty(prop); return str; } bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max) { drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); if (!prop) return false; if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) { drmModeFreeProperty(prop); return false; } if (prop->count_values != 2) abort(); if (min != nullptr) *min = prop->values[0]; if (max != nullptr) *max = prop->values[1]; drmModeFreeProperty(prop); return true; } }; 0707010000003B000081A400000000000000000000000166C38D3F00000281000000000000000000000000000000000000002B00000000aquamarine-0.3.3/src/backend/drm/Props.hpp#pragma once #include <aquamarine/backend/DRM.hpp> namespace Aquamarine { bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out); bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out); bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out); bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret); void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len); char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id); bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max); }; 0707010000003C000081A400000000000000000000000166C38D3F00007A3E000000000000000000000000000000000000002E00000000aquamarine-0.3.3/src/backend/drm/Renderer.cpp#include "Renderer.hpp" #include <xf86drm.h> #include <xf86drmMode.h> #include <cstring> #include <fcntl.h> #include <unistd.h> #include "Math.hpp" #include "Shared.hpp" #include "FormatUtils.hpp" #include <xf86drm.h> #include <aquamarine/allocator/GBM.hpp> using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer #define WP CWeakPointer // static funcs WP<CBackend> gBackend; // ------------------- shader utils GLuint compileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok == GL_FALSE) return 0; return shader; } GLuint createProgram(const std::string& vert, const std::string& frag) { auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert); if (vertCompiled == 0) return 0; auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag); if (fragCompiled == 0) return 0; auto prog = glCreateProgram(); glAttachShader(prog, vertCompiled); glAttachShader(prog, fragCompiled); glLinkProgram(prog); glDetachShader(prog, vertCompiled); glDetachShader(prog, fragCompiled); glDeleteShader(vertCompiled); glDeleteShader(fragCompiled); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) return 0; return prog; } inline const std::string VERT_SRC = R"#( uniform mat3 proj; attribute vec2 pos; attribute vec2 texcoord; varying vec2 v_texcoord; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_texcoord = texcoord; })#"; inline const std::string FRAG_SRC = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex, v_texcoord); })#"; inline const std::string FRAG_SRC_EXT = R"#( #extension GL_OES_EGL_image_external : require precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform samplerExternalOES texture0; void main() { gl_FragColor = texture2D(texture0, v_texcoord); })#"; // ------------------- egl stuff inline void loadGLProc(void* pProc, const char* name) { void* proc = (void*)eglGetProcAddress(name); if (proc == NULL) { gBackend->log(AQ_LOG_ERROR, std::format("eglGetProcAddress({}) failed", name)); abort(); } *(void**)pProc = proc; } // ------------------- std::optional<std::vector<std::pair<uint64_t, bool>>> CDRMRenderer::getModsForFormat(EGLint format) { // TODO: return std::expected when clang supports it EGLint len = 0; if (!egl.eglQueryDmaBufModifiersEXT(egl.display, format, 0, nullptr, nullptr, &len)) { backend->log(AQ_LOG_ERROR, std::format("EGL: eglQueryDmaBufModifiersEXT failed for format {}", fourccToName(format))); return std::nullopt; } if (len <= 0) return std::vector<std::pair<uint64_t, bool>>{}; std::vector<uint64_t> mods; std::vector<EGLBoolean> external; mods.resize(len); external.resize(len); egl.eglQueryDmaBufModifiersEXT(egl.display, format, len, mods.data(), external.data(), &len); std::vector<std::pair<uint64_t, bool>> result; for (size_t i = 0; i < mods.size(); ++i) { result.push_back({mods.at(i), external.at(i)}); } if (std::find(mods.begin(), mods.end(), DRM_FORMAT_MOD_LINEAR) == mods.end() && mods.size() == 0) result.push_back({DRM_FORMAT_MOD_LINEAR, true}); return result; } bool CDRMRenderer::initDRMFormats() { std::vector<EGLint> formats; EGLint len = 0; egl.eglQueryDmaBufFormatsEXT(egl.display, 0, nullptr, &len); formats.resize(len); egl.eglQueryDmaBufFormatsEXT(egl.display, len, formats.data(), &len); if (formats.size() == 0) { backend->log(AQ_LOG_ERROR, "EGL: Failed to get formats"); return false; } TRACE(backend->log(AQ_LOG_TRACE, "EGL: Supported formats:")); std::vector<SGLFormat> dmaFormats; for (auto& fmt : formats) { std::vector<std::pair<uint64_t, bool>> mods; auto ret = getModsForFormat(fmt); if (!ret.has_value()) continue; mods = *ret; hasModifiers = hasModifiers || mods.size() > 0; // EGL can always do implicit modifiers. mods.push_back({DRM_FORMAT_MOD_INVALID, true}); for (auto& [mod, external] : mods) { dmaFormats.push_back(SGLFormat{ .drmFormat = (uint32_t)fmt, .modifier = mod, .external = external, }); } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: GPU Supports Format {} (0x{:x})", fourccToName((uint32_t)fmt), fmt))); for (auto& [mod, external] : mods) { auto modName = drmGetFormatModifierName(mod); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: | {}with modifier 0x{:x}: {}", (external ? "external only " : ""), mod, modName ? modName : "?unknown?"))); free(modName); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: Found {} formats", dmaFormats.size()))); if (dmaFormats.empty()) { backend->log(AQ_LOG_ERROR, "EGL: No formats"); return false; } this->formats = dmaFormats; return true; } SP<CDRMRenderer> CDRMRenderer::attempt(Hyprutils::Memory::CSharedPointer<CGBMAllocator> allocator_, SP<CBackend> backend_) { SP<CDRMRenderer> renderer = SP<CDRMRenderer>(new CDRMRenderer()); renderer->drmFD = allocator_->drmFD(); renderer->backend = backend_; gBackend = backend_; const std::string EGLEXTENSIONS = (const char*)eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!EGLEXTENSIONS.contains("KHR_platform_gbm")) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no gbm support"); return nullptr; } // init egl if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglBindAPI failed"); return nullptr; } loadGLProc(&renderer->egl.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); loadGLProc(&renderer->egl.eglCreateImageKHR, "eglCreateImageKHR"); loadGLProc(&renderer->egl.eglDestroyImageKHR, "eglDestroyImageKHR"); loadGLProc(&renderer->egl.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); loadGLProc(&renderer->egl.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); loadGLProc(&renderer->egl.eglQueryDmaBufFormatsEXT, "eglQueryDmaBufFormatsEXT"); loadGLProc(&renderer->egl.eglQueryDmaBufModifiersEXT, "eglQueryDmaBufModifiersEXT"); loadGLProc(&renderer->egl.eglDestroySyncKHR, "eglDestroySyncKHR"); loadGLProc(&renderer->egl.eglWaitSyncKHR, "eglWaitSyncKHR"); loadGLProc(&renderer->egl.eglCreateSyncKHR, "eglCreateSyncKHR"); loadGLProc(&renderer->egl.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); if (!renderer->egl.eglCreateSyncKHR) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglCreateSyncKHR"); return nullptr; } if (!renderer->egl.eglDupNativeFenceFDANDROID) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglDupNativeFenceFDANDROID"); return nullptr; } if (!renderer->egl.eglGetPlatformDisplayEXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglGetPlatformDisplayEXT"); return nullptr; } if (!renderer->egl.eglCreateImageKHR) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglCreateImageKHR"); return nullptr; } if (!renderer->egl.eglQueryDmaBufFormatsEXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglQueryDmaBufFormatsEXT"); return nullptr; } if (!renderer->egl.eglQueryDmaBufModifiersEXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglQueryDmaBufModifiersEXT"); return nullptr; } std::vector<EGLint> attrs = {EGL_NONE}; renderer->egl.display = renderer->egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, allocator_->gbmDevice, attrs.data()); if (renderer->egl.display == EGL_NO_DISPLAY) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed"); return nullptr; } EGLint major, minor; if (eglInitialize(renderer->egl.display, &major, &minor) == EGL_FALSE) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglInitialize failed"); return nullptr; } attrs.clear(); const std::string EGLEXTENSIONS2 = (const char*)eglQueryString(renderer->egl.display, EGL_EXTENSIONS); if (EGLEXTENSIONS2.contains("IMG_context_priority")) { attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); } if (EGLEXTENSIONS2.contains("EXT_create_context_robustness")) { attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT); } if (!EGLEXTENSIONS2.contains("EXT_image_dma_buf_import_modifiers")) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no EXT_image_dma_buf_import_modifiers ext"); return nullptr; } if (!EGLEXTENSIONS2.contains("EXT_image_dma_buf_import")) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no EXT_image_dma_buf_import ext"); return nullptr; } attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(2); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(0); attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG); attrs.push_back(EGL_FALSE); attrs.push_back(EGL_NONE); renderer->egl.context = eglCreateContext(renderer->egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (renderer->egl.context == EGL_NO_CONTEXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglCreateContext failed"); return nullptr; } if (EGLEXTENSIONS2.contains("IMG_context_priority")) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(renderer->egl.display, renderer->egl.context, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: didnt get a high priority context"); else backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: got a high priority context"); } // init shaders renderer->setEGL(); if (!renderer->initDRMFormats()) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, initDRMFormats failed"); return nullptr; } renderer->gl.shader.program = createProgram(VERT_SRC, FRAG_SRC); if (renderer->gl.shader.program == 0) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, shader failed"); return nullptr; } renderer->gl.shader.proj = glGetUniformLocation(renderer->gl.shader.program, "proj"); renderer->gl.shader.posAttrib = glGetAttribLocation(renderer->gl.shader.program, "pos"); renderer->gl.shader.texAttrib = glGetAttribLocation(renderer->gl.shader.program, "texcoord"); renderer->gl.shader.tex = glGetUniformLocation(renderer->gl.shader.program, "tex"); renderer->gl.shaderExt.program = createProgram(VERT_SRC, FRAG_SRC_EXT); if (renderer->gl.shaderExt.program == 0) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, shaderExt failed"); return nullptr; } renderer->gl.shaderExt.proj = glGetUniformLocation(renderer->gl.shaderExt.program, "proj"); renderer->gl.shaderExt.posAttrib = glGetAttribLocation(renderer->gl.shaderExt.program, "pos"); renderer->gl.shaderExt.texAttrib = glGetAttribLocation(renderer->gl.shaderExt.program, "texcoord"); renderer->gl.shaderExt.tex = glGetUniformLocation(renderer->gl.shaderExt.program, "tex"); renderer->restoreEGL(); backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: success"); return renderer; } void CDRMRenderer::setEGL() { savedEGLState.display = eglGetCurrentDisplay(); savedEGLState.context = eglGetCurrentContext(); savedEGLState.draw = eglGetCurrentSurface(EGL_DRAW); savedEGLState.read = eglGetCurrentSurface(EGL_READ); if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl.context)) backend->log(AQ_LOG_WARNING, "CDRMRenderer: setEGL eglMakeCurrent failed"); } void CDRMRenderer::restoreEGL() { EGLDisplay dpy = savedEGLState.display ? savedEGLState.display : egl.display; // egl can't handle this if (dpy == EGL_NO_DISPLAY) return; if (!eglMakeCurrent(dpy, savedEGLState.draw, savedEGLState.read, savedEGLState.context)) backend->log(AQ_LOG_WARNING, "CDRMRenderer: restoreEGL eglMakeCurrent failed"); } EGLImageKHR CDRMRenderer::createEGLImage(const SDMABUFAttrs& attrs) { std::vector<uint32_t> attribs; attribs.push_back(EGL_WIDTH); attribs.push_back(attrs.size.x); attribs.push_back(EGL_HEIGHT); attribs.push_back(attrs.size.y); attribs.push_back(EGL_LINUX_DRM_FOURCC_EXT); attribs.push_back(attrs.format); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: createEGLImage: size {} with format {} and modifier 0x{:x}", attrs.size, fourccToName(attrs.format), attrs.modifier))); struct { EGLint fd; EGLint offset; EGLint pitch; EGLint modlo; EGLint modhi; } attrNames[4] = { {EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, {EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; for (int i = 0; i < attrs.planes; i++) { attribs.push_back(attrNames[i].fd); attribs.push_back(attrs.fds[i]); attribs.push_back(attrNames[i].offset); attribs.push_back(attrs.offsets[i]); attribs.push_back(attrNames[i].pitch); attribs.push_back(attrs.strides[i]); if (hasModifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { attribs.push_back(attrNames[i].modlo); attribs.push_back(attrs.modifier & 0xFFFFFFFF); attribs.push_back(attrNames[i].modhi); attribs.push_back(attrs.modifier >> 32); } } attribs.push_back(EGL_IMAGE_PRESERVED_KHR); attribs.push_back(EGL_TRUE); attribs.push_back(EGL_NONE); EGLImageKHR image = egl.eglCreateImageKHR(egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, (int*)attribs.data()); if (image == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL: EGLCreateImageKHR failed: {}", eglGetError())); return EGL_NO_IMAGE_KHR; } return image; } #define GLCALL(__CALL__) \ { \ __CALL__; \ auto err = glGetError(); \ if (err != GL_NO_ERROR) { \ backend->log(AQ_LOG_ERROR, \ std::format("[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err)); \ } \ } SGLTex CDRMRenderer::glTex(Hyprutils::Memory::CSharedPointer<IBuffer> buffa) { SGLTex tex; const auto dma = buffa->dmabuf(); tex.image = createEGLImage(dma); if (tex.image == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (glTex): createEGLImage failed: {}", eglGetError())); return tex; } bool external = false; for (auto& fmt : formats) { if (fmt.drmFormat != dma.format || fmt.modifier != dma.modifier) continue; backend->log(AQ_LOG_DEBUG, std::format("CDRMRenderer::glTex: found format+mod, external = {}", fmt.external)); external = fmt.external; break; } tex.target = external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; GLCALL(glGenTextures(1, &tex.texid)); GLCALL(glBindTexture(tex.target, tex.texid)); GLCALL(glTexParameteri(tex.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLCALL(glTexParameteri(tex.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GLCALL(egl.glEGLImageTargetTexture2DOES(tex.target, tex.image)); GLCALL(glBindTexture(tex.target, 0)); return tex; } inline const float fullVerts[] = { 1, 0, // top right 0, 0, // top left 1, 1, // bottom right 0, 1, // bottom left }; void CDRMRenderer::waitOnSync(int fd) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (waitOnSync): attempting to wait on fd {}", fd))); std::vector<EGLint> attribs; int dupFd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (dupFd < 0) { backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to dup fd for wait"); return; } attribs.push_back(EGL_SYNC_NATIVE_FENCE_FD_ANDROID); attribs.push_back(dupFd); attribs.push_back(EGL_NONE); EGLSyncKHR sync = egl.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs.data()); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to create an egl sync for explicit")); if (dupFd >= 0) close(dupFd); return; } // we got a sync, now we just tell egl to wait before sampling if (egl.eglWaitSyncKHR(egl.display, sync, 0) != EGL_TRUE) { if (egl.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to wait on the sync object")); return; } if (egl.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); } int CDRMRenderer::recreateBlitSync() { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): recreating blit sync")); if (egl.lastBlitSync) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): cleaning up old sync (fd {})", egl.lastBlitSyncFD))); // cleanup last sync if (egl.eglDestroySyncKHR(egl.display, egl.lastBlitSync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy old sync")); if (egl.lastBlitSyncFD >= 0) close(egl.lastBlitSyncFD); egl.lastBlitSyncFD = -1; egl.lastBlitSync = nullptr; } EGLSyncKHR sync = egl.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to create an egl sync for explicit")); return -1; } // we need to flush otherwise we might not get a valid fd glFlush(); int fd = egl.eglDupNativeFenceFDANDROID(egl.display, sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to dup egl fence fd")); if (egl.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy new sync")); return -1; } egl.lastBlitSync = sync; egl.lastBlitSyncFD = fd; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): success, new fence exported with fd {}", fd))); return fd; } void CDRMRenderer::clearBuffer(IBuffer* buf) { setEGL(); auto dmabuf = buf->dmabuf(); GLuint rboID = 0, fboID = 0; if (!dmabuf.success) { backend->log(AQ_LOG_ERROR, "EGL (clear): cannot clear a non-dmabuf"); return; } auto rboImage = createEGLImage(dmabuf); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (clear): createEGLImage failed: {}", eglGetError())); return; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(egl.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &fboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (clear): fbo {} rbo {}", fboID, rboID))); glClearColor(0.F, 0.F, 0.F, 1.F); glClear(GL_COLOR_BUFFER_BIT); glFlush(); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); glDeleteFramebuffers(1, &fboID); glDeleteRenderbuffers(1, &rboID); egl.eglDestroyImageKHR(egl.display, rboImage); restoreEGL(); } CDRMRenderer::SBlitResult CDRMRenderer::blit(SP<IBuffer> from, SP<IBuffer> to, int waitFD) { setEGL(); if (from->dmabuf().size != to->dmabuf().size) { backend->log(AQ_LOG_ERROR, "EGL (blit): buffer sizes mismatched"); return {}; } if (waitFD >= 0) { // wait on a provided explicit fence waitOnSync(waitFD); } // firstly, get a texture from the from buffer // if it has an attachment, use that // both from and to have the same AQ_ATTACHMENT_DRM_RENDERER_DATA. // Those buffers always come from different swapchains, so it's OK. SGLTex fromTex; { auto attachment = from->attachments.get(AQ_ATTACHMENT_DRM_RENDERER_DATA); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): From attachment found")); auto att = (CDRMRendererBufferAttachment*)attachment.get(); fromTex = att->tex; } if (!fromTex.image) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in from, creating a new image"); fromTex = glTex(from); // should never remove anything, but JIC. We'll leak an EGLImage if this removes anything. from->attachments.removeByType(AQ_ATTACHMENT_DRM_RENDERER_DATA); from->attachments.add(makeShared<CDRMRendererBufferAttachment>(self, from, nullptr, 0, 0, fromTex)); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fromTex id {}, image 0x{:x}, target {}", fromTex.texid, (uintptr_t)fromTex.image, fromTex.target == GL_TEXTURE_2D ? "GL_TEXTURE_2D" : "GL_TEXTURE_EXTERNAL_OES"))); // then, get a rbo from our to buffer // if it has an attachment, use that EGLImageKHR rboImage = nullptr; GLuint rboID = 0, fboID = 0; auto toDma = to->dmabuf(); if (!verifyDestinationDMABUF(toDma)) { backend->log(AQ_LOG_ERROR, "EGL (blit): failed to blit: destination dmabuf unsupported"); return {}; } { auto attachment = to->attachments.get(AQ_ATTACHMENT_DRM_RENDERER_DATA); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): To attachment found")); auto att = (CDRMRendererBufferAttachment*)attachment.get(); rboImage = att->eglImage; fboID = att->fbo; rboID = att->rbo; } if (!rboImage) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in to, creating a new image"); rboImage = createEGLImage(toDma); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (blit): createEGLImage failed: {}", eglGetError())); return {}; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(egl.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &fboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { backend->log(AQ_LOG_ERROR, std::format("EGL (blit): glCheckFramebufferStatus failed: {}", glGetError())); return {}; } // should never remove anything, but JIC. We'll leak an RBO and FBO if this removes anything. to->attachments.removeByType(AQ_ATTACHMENT_DRM_RENDERER_DATA); to->attachments.add(makeShared<CDRMRendererBufferAttachment>(self, to, rboImage, fboID, rboID, SGLTex{})); } } glFlush(); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): rboImage 0x{:x}", (uintptr_t)rboImage))); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fbo {} rbo {}", fboID, rboID))); glClearColor(0.77F, 0.F, 0.74F, 1.F); glClear(GL_COLOR_BUFFER_BIT); // done, let's render the texture to the rbo CBox renderBox = {{}, toDma.size}; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): box size {}", renderBox.size()))); float mtx[9]; float base[9]; float monitorProj[9]; matrixIdentity(base); auto& SHADER = fromTex.target == GL_TEXTURE_2D ? gl.shader : gl.shaderExt; // KMS uses flipped y, we have to do FLIPPED_180 matrixTranslate(base, toDma.size.x / 2.0, toDma.size.y / 2.0); matrixTransform(base, HYPRUTILS_TRANSFORM_FLIPPED_180); matrixTranslate(base, -toDma.size.x / 2.0, -toDma.size.y / 2.0); projectBox(mtx, renderBox, HYPRUTILS_TRANSFORM_FLIPPED_180, 0, base); matrixProjection(monitorProj, toDma.size.x, toDma.size.y, HYPRUTILS_TRANSFORM_FLIPPED_180); float glMtx[9]; matrixMultiply(glMtx, monitorProj, mtx); GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y)); GLCALL(glActiveTexture(GL_TEXTURE0)); GLCALL(glBindTexture(fromTex.target, fromTex.texid)); GLCALL(glTexParameteri(fromTex.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GLCALL(glTexParameteri(fromTex.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); GLCALL(glUseProgram(SHADER.program)); GLCALL(glDisable(GL_BLEND)); GLCALL(glDisable(GL_SCISSOR_TEST)); matrixTranspose(glMtx, glMtx); GLCALL(glUniformMatrix3fv(SHADER.proj, 1, GL_FALSE, glMtx)); GLCALL(glUniform1i(SHADER.tex, 0)); GLCALL(glVertexAttribPointer(SHADER.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts)); GLCALL(glVertexAttribPointer(SHADER.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts)); GLCALL(glEnableVertexAttribArray(SHADER.posAttrib)); GLCALL(glEnableVertexAttribArray(SHADER.texAttrib)); GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); GLCALL(glDisableVertexAttribArray(SHADER.posAttrib)); GLCALL(glDisableVertexAttribArray(SHADER.texAttrib)); GLCALL(glBindTexture(fromTex.target, 0)); // rendered, cleanup glFlush(); // get an explicit sync fd for the secondary gpu. // when we pass buffers between gpus we should always use explicit sync, // as implicit is not guaranteed at all int explicitFD = recreateBlitSync(); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); restoreEGL(); return {true, explicitFD == -1 ? std::nullopt : std::optional<int>{explicitFD}}; } void CDRMRenderer::onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachment) { setEGL(); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (onBufferAttachmentDrop): dropping fbo {} rbo {} image 0x{:x}", attachment->fbo, attachment->rbo, (uintptr_t)attachment->eglImage))); if (attachment->tex.texid) GLCALL(glDeleteTextures(1, &attachment->tex.texid)); if (attachment->rbo) GLCALL(glDeleteRenderbuffers(1, &attachment->rbo)); if (attachment->fbo) GLCALL(glDeleteFramebuffers(1, &attachment->fbo)); if (attachment->eglImage) egl.eglDestroyImageKHR(egl.display, attachment->eglImage); if (attachment->tex.image) egl.eglDestroyImageKHR(egl.display, attachment->tex.image); restoreEGL(); } bool CDRMRenderer::verifyDestinationDMABUF(const SDMABUFAttrs& attrs) { for (auto& fmt : formats) { if (fmt.drmFormat != attrs.format) continue; if (fmt.modifier != attrs.modifier) continue; if (fmt.external) { backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is external-only"); return false; } return true; } backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is unsupported by EGL"); return false; } CDRMRendererBufferAttachment::CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer_, Hyprutils::Memory::CSharedPointer<IBuffer> buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, SGLTex tex_) : eglImage(image), fbo(fbo_), rbo(rbo_), renderer(renderer_), tex(tex_) { bufferDestroy = buffer->events.destroy.registerListener([this](std::any d) { renderer->onBufferAttachmentDrop(this); }); } 0707010000003D000081A400000000000000000000000166C38D3F000013ED000000000000000000000000000000000000002E00000000aquamarine-0.3.3/src/backend/drm/Renderer.hpp#pragma once #include <aquamarine/backend/DRM.hpp> #include "FormatUtils.hpp" #include <EGL/egl.h> #include <EGL/eglext.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <gbm.h> #include <optional> #include <tuple> #include <vector> namespace Aquamarine { class CGBMAllocator; struct SGLTex { EGLImage image = nullptr; GLuint texid = 0; GLuint target = GL_TEXTURE_2D; }; class CDRMRendererBufferAttachment : public IAttachment { public: CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer_, Hyprutils::Memory::CSharedPointer<IBuffer> buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, SGLTex tex); virtual ~CDRMRendererBufferAttachment() { ; } virtual eAttachmentType type() { return AQ_ATTACHMENT_DRM_RENDERER_DATA; } EGLImageKHR eglImage = nullptr; GLuint fbo = 0, rbo = 0; SGLTex tex; Hyprutils::Signal::CHyprSignalListener bufferDestroy; Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer; }; class CDRMRenderer { public: static Hyprutils::Memory::CSharedPointer<CDRMRenderer> attempt(Hyprutils::Memory::CSharedPointer<CGBMAllocator> allocator_, Hyprutils::Memory::CSharedPointer<CBackend> backend_); int drmFD = -1; struct SBlitResult { bool success = false; std::optional<int> syncFD; }; SBlitResult blit(Hyprutils::Memory::CSharedPointer<IBuffer> from, Hyprutils::Memory::CSharedPointer<IBuffer> to, int waitFD = -1); // can't be a SP<> because we call it from buf's ctor... void clearBuffer(IBuffer* buf); void setEGL(); void restoreEGL(); void onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachment); struct { struct SShader { GLuint program = 0; GLint proj = -1, tex = -1, posAttrib = -1, texAttrib = -1; } shader, shaderExt; } gl; struct { EGLDisplay display = nullptr; EGLContext context = nullptr; EGLSync lastBlitSync = nullptr; int lastBlitSyncFD = -1; PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr; PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT = nullptr; PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; } egl; struct { EGLDisplay display = nullptr; EGLContext context = nullptr; EGLSurface draw = nullptr, read = nullptr; } savedEGLState; SGLTex glTex(Hyprutils::Memory::CSharedPointer<IBuffer> buf); Hyprutils::Memory::CWeakPointer<CDRMRenderer> self; std::vector<SGLFormat> formats; private: CDRMRenderer() = default; EGLImageKHR createEGLImage(const SDMABUFAttrs& attrs); std::optional<std::vector<std::pair<uint64_t, bool>>> getModsForFormat(EGLint format); bool initDRMFormats(); bool verifyDestinationDMABUF(const SDMABUFAttrs& attrs); void waitOnSync(int fd); int recreateBlitSync(); bool hasModifiers = false; Hyprutils::Memory::CWeakPointer<CBackend> backend; }; };0707010000003E000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000002600000000aquamarine-0.3.3/src/backend/drm/impl0707010000003F000081A400000000000000000000000166C38D3F0000367E000000000000000000000000000000000000003100000000aquamarine-0.3.3/src/backend/drm/impl/Atomic.cpp#include <aquamarine/backend/drm/Atomic.hpp> #include <cstring> #include <xf86drm.h> #include <xf86drmMode.h> #include <sys/mman.h> #include "Shared.hpp" #include "aquamarine/output/Output.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CDRMAtomicRequest::CDRMAtomicRequest(Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_) : backend(backend_) { req = drmModeAtomicAlloc(); if (!req) failed = true; } Aquamarine::CDRMAtomicRequest::~CDRMAtomicRequest() { if (req) drmModeAtomicFree(req); } void Aquamarine::CDRMAtomicRequest::add(uint32_t id, uint32_t prop, uint64_t val) { if (failed) return; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic drm request: adding id {} prop {} with value {}", id, prop, val))); if (id == 0 || prop == 0) { backend->log(AQ_LOG_ERROR, "atomic drm request: failed to add prop: id / prop == 0"); return; } if (drmModeAtomicAddProperty(req, id, prop, val) < 0) { backend->log(AQ_LOG_ERROR, "atomic drm request: failed to add prop"); failed = true; } } void Aquamarine::CDRMAtomicRequest::planeProps(Hyprutils::Memory::CSharedPointer<SDRMPlane> plane, Hyprutils::Memory::CSharedPointer<CDRMFB> fb, uint32_t crtc, Hyprutils::Math::Vector2D pos) { if (failed) return; if (!fb || !crtc) { // Disable the plane TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: disabling plane {}", plane->id))); add(plane->id, plane->props.fb_id, 0); add(plane->id, plane->props.crtc_id, 0); add(plane->id, plane->props.crtc_x, (uint64_t)pos.x); add(plane->id, plane->props.crtc_y, (uint64_t)pos.y); return; } TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: prop blobs: src_x {}, src_y {}, src_w {}, src_h {}, crtc_w {}, crtc_h {}, fb_id {}, crtc_id {}, crtc_x {}, crtc_y {}", plane->props.src_x, plane->props.src_y, plane->props.src_w, plane->props.src_h, plane->props.crtc_w, plane->props.crtc_h, plane->props.fb_id, plane->props.crtc_id, plane->props.crtc_x, plane->props.crtc_y))); // src_ are 16.16 fixed point (lol) add(plane->id, plane->props.src_x, 0); add(plane->id, plane->props.src_y, 0); add(plane->id, plane->props.src_w, ((uint64_t)fb->buffer->size.x) << 16); add(plane->id, plane->props.src_h, ((uint64_t)fb->buffer->size.y) << 16); add(plane->id, plane->props.crtc_w, (uint32_t)fb->buffer->size.x); add(plane->id, plane->props.crtc_h, (uint32_t)fb->buffer->size.y); add(plane->id, plane->props.fb_id, fb->id); add(plane->id, plane->props.crtc_id, crtc); add(plane->id, plane->props.crtc_x, (uint64_t)pos.x); add(plane->id, plane->props.crtc_y, (uint64_t)pos.y); } void Aquamarine::CDRMAtomicRequest::addConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled && data.mainFB; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic addConnector blobs: mode_id {}, active {}, crtc_id {}, link_status {}, content_type {}", connector->crtc->props.mode_id, connector->crtc->props.active, connector->props.crtc_id, connector->props.link_status, connector->props.content_type))); TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic addConnector values: CRTC {}, mode {}", enable ? connector->crtc->id : 0, data.atomic.modeBlob))); add(connector->id, connector->props.crtc_id, enable ? connector->crtc->id : 0); if (data.modeset) { add(connector->crtc->id, connector->crtc->props.mode_id, data.atomic.modeBlob); data.atomic.blobbed = true; } if (data.modeset && enable && connector->props.link_status) add(connector->id, connector->props.link_status, DRM_MODE_LINK_STATUS_GOOD); // TODO: allow to send aq a content type, maybe? Wayland has a protocol for this. if (enable && connector->props.content_type) add(connector->id, connector->props.content_type, DRM_MODE_CONTENT_TYPE_GRAPHICS); if (data.modeset && enable && connector->props.max_bpc && connector->maxBpcBounds.at(1)) add(connector->id, connector->props.max_bpc, 8); // FIXME: this isnt always 8 add(connector->crtc->id, connector->crtc->props.active, enable); if (enable) { if (connector->output->supportsExplicit && STATE.committed & COutputState::AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE) add(connector->crtc->id, connector->crtc->props.out_fence_ptr, (uintptr_t)&STATE.explicitOutFence); if (connector->crtc->props.gamma_lut && data.atomic.gammad) add(connector->crtc->id, connector->crtc->props.gamma_lut, data.atomic.gammaLut); if (connector->crtc->props.vrr_enabled) add(connector->crtc->id, connector->crtc->props.vrr_enabled, (uint64_t)STATE.adaptiveSync); planeProps(connector->crtc->primary, data.mainFB, connector->crtc->id, {}); if (connector->output->supportsExplicit && STATE.explicitInFence >= 0) add(connector->crtc->primary->id, connector->crtc->primary->props.in_fence_fd, STATE.explicitInFence); if (connector->crtc->primary->props.fb_damage_clips) add(connector->crtc->primary->id, connector->crtc->primary->props.fb_damage_clips, data.atomic.fbDamage); if (connector->crtc->cursor) { if (!connector->output->cursorVisible) planeProps(connector->crtc->cursor, nullptr, 0, {}); else planeProps(connector->crtc->cursor, data.cursorFB, connector->crtc->id, connector->output->cursorPos - connector->output->cursorHotspot); } } else { planeProps(connector->crtc->primary, nullptr, 0, {}); if (connector->crtc->cursor) planeProps(connector->crtc->cursor, nullptr, 0, {}); } conn = connector; } bool Aquamarine::CDRMAtomicRequest::commit(uint32_t flagssss) { static auto flagsToStr = [](uint32_t flags) { std::string result; if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) result += "ATOMIC_ALLOW_MODESET "; if (flags & DRM_MODE_ATOMIC_NONBLOCK) result += "ATOMIC_NONBLOCK "; if (flags & DRM_MODE_ATOMIC_TEST_ONLY) result += "ATOMIC_TEST_ONLY "; if (flags & DRM_MODE_PAGE_FLIP_EVENT) result += "PAGE_FLIP_EVENT "; if (flags & DRM_MODE_PAGE_FLIP_ASYNC) result += "PAGE_FLIP_ASYNC "; if (flags & (~DRM_MODE_ATOMIC_FLAGS)) result += " + invalid..."; return result; }; if (failed) { backend->log((flagssss & DRM_MODE_ATOMIC_TEST_ONLY) ? AQ_LOG_DEBUG : AQ_LOG_ERROR, std::format("atomic drm request: failed to commit, failed flag set to true")); return false; } if (auto ret = drmModeAtomicCommit(backend->gpu->fd, req, flagssss, &conn->pendingPageFlip); ret) { backend->log((flagssss & DRM_MODE_ATOMIC_TEST_ONLY) ? AQ_LOG_DEBUG : AQ_LOG_ERROR, std::format("atomic drm request: failed to commit: {}, flags: {}", strerror(-ret), flagsToStr(flagssss))); return false; } return true; } void Aquamarine::CDRMAtomicRequest::destroyBlob(uint32_t id) { if (!id) return; if (drmModeDestroyPropertyBlob(backend->gpu->fd, id)) backend->log(AQ_LOG_ERROR, "atomic drm request: failed to destroy a blob"); } void Aquamarine::CDRMAtomicRequest::commitBlob(uint32_t* current, uint32_t next) { if (*current == next) return; destroyBlob(*current); *current = next; } void Aquamarine::CDRMAtomicRequest::rollbackBlob(uint32_t* current, uint32_t next) { if (*current == next) return; destroyBlob(next); } void Aquamarine::CDRMAtomicRequest::rollback(SDRMConnectorCommitData& data) { if (!conn) return; conn->crtc->atomic.ownModeID = true; if (data.atomic.blobbed) rollbackBlob(&conn->crtc->atomic.modeID, data.atomic.modeBlob); rollbackBlob(&conn->crtc->atomic.gammaLut, data.atomic.gammaLut); destroyBlob(data.atomic.fbDamage); } void Aquamarine::CDRMAtomicRequest::apply(SDRMConnectorCommitData& data) { if (!conn) return; if (!conn->crtc->atomic.ownModeID) conn->crtc->atomic.modeID = 0; conn->crtc->atomic.ownModeID = true; if (data.atomic.blobbed) commitBlob(&conn->crtc->atomic.modeID, data.atomic.modeBlob); commitBlob(&conn->crtc->atomic.gammaLut, data.atomic.gammaLut); destroyBlob(data.atomic.fbDamage); } Aquamarine::CDRMAtomicImpl::CDRMAtomicImpl(Hyprutils::Memory::CSharedPointer<CDRMBackend> backend_) : backend(backend_) { ; } bool Aquamarine::CDRMAtomicImpl::prepareConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled; const auto& MODE = STATE.mode ? STATE.mode : STATE.customMode; if (data.modeset) { if (!enable) data.atomic.modeBlob = 0; else { if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, (drmModeModeInfo*)&data.modeInfo, sizeof(drmModeModeInfo), &data.atomic.modeBlob)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a modeset blob"); return false; } TRACE(connector->backend->log(AQ_LOG_TRACE, std::format("Connector blob id {}: clock {}, {}x{}, vrefresh {}, name: {}", data.atomic.modeBlob, data.modeInfo.clock, data.modeInfo.hdisplay, data.modeInfo.vdisplay, data.modeInfo.vrefresh, data.modeInfo.name))); } } if (STATE.committed & COutputState::AQ_OUTPUT_STATE_GAMMA_LUT) { if (!connector->crtc->props.gamma_lut) // TODO: allow this with legacy gamma, perhaps. connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to commit gamma: no gamma_lut prop"); else if (STATE.gammaLut.empty()) { data.atomic.gammaLut = 0; data.atomic.gammad = true; } else { std::vector<drm_color_lut> lut; lut.resize(STATE.gammaLut.size() / 3); // [r,g,b]+ for (size_t i = 0; i < lut.size(); ++i) { lut.at(i).red = STATE.gammaLut.at(i * 3 + 0); lut.at(i).green = STATE.gammaLut.at(i * 3 + 1); lut.at(i).blue = STATE.gammaLut.at(i * 3 + 2); lut.at(i).reserved = 0; } if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, lut.data(), lut.size() * sizeof(drm_color_lut), &data.atomic.gammaLut)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a gamma blob"); data.atomic.gammaLut = 0; } else data.atomic.gammad = true; } } if ((STATE.committed & COutputState::AQ_OUTPUT_STATE_DAMAGE) && connector->crtc->primary->props.fb_damage_clips && MODE) { if (STATE.damage.empty()) data.atomic.fbDamage = 0; else { TRACE(connector->backend->backend->log(AQ_LOG_TRACE, std::format("atomic drm: clipping damage to pixel size {}", MODE->pixelSize))); std::vector<pixman_box32_t> rects = STATE.damage.copy().intersect(CBox{{}, MODE->pixelSize}).getRects(); if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, rects.data(), sizeof(pixman_box32_t) * rects.size(), &data.atomic.fbDamage)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a damage blob"); return false; } } } return true; } bool Aquamarine::CDRMAtomicImpl::commit(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { if (!prepareConnector(connector, data)) return false; CDRMAtomicRequest request(backend); request.addConnector(connector, data); uint32_t flags = data.flags; if (data.test) flags |= DRM_MODE_ATOMIC_TEST_ONLY; if (data.modeset) flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; if (!data.blocking && !data.test) flags |= DRM_MODE_ATOMIC_NONBLOCK; const bool ok = request.commit(flags); if (ok) { request.apply(data); if (!data.test && data.mainFB && connector->output->state->state().enabled && (flags & DRM_MODE_PAGE_FLIP_EVENT)) connector->isPageFlipPending = true; } else request.rollback(data); return ok; } bool Aquamarine::CDRMAtomicImpl::reset() { CDRMAtomicRequest request(backend); for (auto& crtc : backend->crtcs) { request.add(crtc->id, crtc->props.mode_id, 0); request.add(crtc->id, crtc->props.active, 0); } for (auto& conn : backend->connectors) { request.add(conn->id, conn->props.crtc_id, 0); } for (auto& plane : backend->planes) { request.planeProps(plane, nullptr, 0, {}); } return request.commit(DRM_MODE_ATOMIC_ALLOW_MODESET); } bool Aquamarine::CDRMAtomicImpl::moveCursor(SP<SDRMConnector> connector, bool skipShedule) { if (!connector->output->cursorVisible || !connector->output->state->state().enabled || !connector->crtc || !connector->crtc->cursor) return true; if (!skipShedule) { TRACE(connector->backend->log(AQ_LOG_TRACE, "atomic moveCursor")); connector->output->scheduleFrame(IOutput::AQ_SCHEDULE_CURSOR_MOVE); } return true; }07070100000040000081A400000000000000000000000166C38D3F00001C4A000000000000000000000000000000000000003100000000aquamarine-0.3.3/src/backend/drm/impl/Legacy.cpp#include "aquamarine/output/Output.hpp" #include <aquamarine/backend/drm/Legacy.hpp> #include <cstring> #include <xf86drm.h> #include <xf86drmMode.h> #include <sys/mman.h> using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CDRMLegacyImpl::CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer<CDRMBackend> backend_) : backend(backend_) { ; } bool Aquamarine::CDRMLegacyImpl::moveCursor(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, bool skipShedule) { if (!connector->output->cursorVisible || !connector->output->state->state().enabled || !connector->crtc || !connector->crtc->cursor) return true; if (!skipShedule) connector->output->scheduleFrame(IOutput::AQ_SCHEDULE_CURSOR_MOVE); return true; } bool Aquamarine::CDRMLegacyImpl::commitInternal(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); SP<CDRMFB> mainFB; bool enable = STATE.enabled; if (enable) { if (!data.mainFB) connector->backend->backend->log(AQ_LOG_WARNING, "legacy drm: No buffer, will fall back to only modeset (if present)"); else mainFB = data.mainFB; } if (data.modeset) { connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC {}", connector->crtc->id)); uint32_t dpms = enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF; if (drmModeConnectorSetProperty(connector->backend->gpu->fd, connector->id, connector->props.dpms, dpms)) { connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: Failed to set dpms"); return false; } std::vector<uint32_t> connectors; drmModeModeInfo* mode = nullptr; if (enable) { connectors.push_back(connector->id); mode = (drmModeModeInfo*)&data.modeInfo; } if (mode) { connector->backend->backend->log( AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC, mode: clock {} hdisplay {} vdisplay {} vrefresh {}", mode->clock, mode->hdisplay, mode->vdisplay, mode->vrefresh)); } else connector->backend->backend->log(AQ_LOG_DEBUG, "legacy drm: Modesetting CRTC, mode null"); if (auto ret = drmModeSetCrtc(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, 0, 0, connectors.data(), connectors.size(), mode); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModeSetCrtc failed: {}", strerror(-ret))); return false; } } if (STATE.committed & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ADAPTIVE_SYNC) { if (STATE.adaptiveSync && !connector->canDoVrr) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: connector {} can't do vrr", connector->id)); return false; } if (connector->crtc->props.vrr_enabled) { if (auto ret = drmModeObjectSetProperty(backend->gpu->fd, connector->crtc->id, DRM_MODE_OBJECT_CRTC, connector->crtc->props.vrr_enabled, (uint64_t)STATE.adaptiveSync); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModeObjectSetProperty: vrr -> {} failed: {}", STATE.adaptiveSync, strerror(-ret))); return false; } } connector->output->vrrActive = STATE.adaptiveSync; connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: connector {} vrr -> {}", connector->id, STATE.adaptiveSync)); } // TODO: gamma if (data.cursorFB && connector->crtc->cursor && connector->output->cursorVisible && enable) { uint32_t boHandle = 0; auto attrs = data.cursorFB->buffer->dmabuf(); if (int ret = drmPrimeFDToHandle(connector->backend->gpu->fd, attrs.fds.at(0), &boHandle); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmPrimeFDToHandle failed: {}", strerror(-ret))); return false; } connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: cursor fb: {} with bo handle {} from fd {}, size {}", connector->backend->gpu->fd, boHandle, data.cursorFB->buffer->dmabuf().fds.at(0), data.cursorFB->buffer->size)); Vector2D cursorPos = connector->output->cursorPos; struct drm_mode_cursor2 request = { .flags = DRM_MODE_CURSOR_BO | DRM_MODE_CURSOR_MOVE, .crtc_id = connector->crtc->id, .x = (int32_t)cursorPos.x, .y = (int32_t)cursorPos.y, .width = (uint32_t)data.cursorFB->buffer->size.x, .height = (uint32_t)data.cursorFB->buffer->size.y, .handle = boHandle, .hot_x = (int32_t)connector->output->cursorHotspot.x, .hot_y = (int32_t)connector->output->cursorHotspot.y, }; int ret = drmIoctl(connector->backend->gpu->fd, DRM_IOCTL_MODE_CURSOR2, &request); if (boHandle && drmCloseBufferHandle(connector->backend->gpu->fd, boHandle)) connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: drmCloseBufferHandle in cursor failed"); if (ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: cursor drmIoctl failed: {}", strerror(errno))); return false; } } else if (drmModeSetCursor(connector->backend->gpu->fd, connector->crtc->id, 0, 0, 0)) connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: cursor null failed"); if (!enable) return true; if (!(data.flags & DRM_MODE_PAGE_FLIP_EVENT)) return true; if (int ret = drmModePageFlip(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, data.flags, &connector->pendingPageFlip); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModePageFlip failed: {}", strerror(-ret))); return false; } connector->isPageFlipPending = true; return true; } bool Aquamarine::CDRMLegacyImpl::testInternal(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { return true; // TODO: lol } bool Aquamarine::CDRMLegacyImpl::commit(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { if (!testInternal(connector, data)) return false; return commitInternal(connector, data); } bool Aquamarine::CDRMLegacyImpl::reset() { bool ok = true; for (auto& connector : backend->connectors) { if (!connector->crtc) continue; if (int ret = drmModeSetCrtc(backend->gpu->fd, connector->crtc->id, 0, 0, 0, nullptr, 0, nullptr); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: reset failed: {}", strerror(-ret))); ok = false; } } return ok; }07070100000041000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001C00000000aquamarine-0.3.3/src/buffer07070100000042000081A400000000000000000000000166C38D3F000002B7000000000000000000000000000000000000002700000000aquamarine-0.3.3/src/buffer/Buffer.cpp#include <aquamarine/buffer/Buffer.hpp> #include "Shared.hpp" using namespace Aquamarine; SDMABUFAttrs Aquamarine::IBuffer::dmabuf() { return SDMABUFAttrs{}; } SSHMAttrs Aquamarine::IBuffer::shm() { return SSHMAttrs{}; } std::tuple<uint8_t*, uint32_t, size_t> Aquamarine::IBuffer::beginDataPtr(uint32_t flags) { return {nullptr, 0, 0}; } void Aquamarine::IBuffer::endDataPtr() { ; // empty } void Aquamarine::IBuffer::sendRelease() { ; } void Aquamarine::IBuffer::lock() { locks++; } void Aquamarine::IBuffer::unlock() { locks--; ASSERT(locks >= 0); if (locks <= 0) sendRelease(); } bool Aquamarine::IBuffer::locked() { return locks; } 07070100000043000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001D00000000aquamarine-0.3.3/src/include07070100000044000081A400000000000000000000000166C38D3F00000061000000000000000000000000000000000000002D00000000aquamarine-0.3.3/src/include/FormatUtils.hpp#pragma once #include <string> #include <cstdint> std::string fourccToName(uint32_t drmFormat);07070100000045000081A400000000000000000000000166C38D3F00000957000000000000000000000000000000000000002800000000aquamarine-0.3.3/src/include/Shared.hpp#pragma once #include <iostream> #include <format> #include <signal.h> namespace Aquamarine { bool envEnabled(const std::string& env); bool isTrace(); }; #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 << "[Aquamarine] Assertion failed!"; \ raise(SIGABRT); \ } #define ASSERT(expr) RASSERT(expr, "?") #define TRACE(expr) \ { \ if (Aquamarine::isTrace()) { \ expr; \ } \ } 07070100000046000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001B00000000aquamarine-0.3.3/src/input07070100000047000081A400000000000000000000000166C38D3F000002B5000000000000000000000000000000000000002500000000aquamarine-0.3.3/src/input/Input.cpp#include <aquamarine/input/Input.hpp> libinput_device* Aquamarine::IPointer::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::IKeyboard::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITouch::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ISwitch::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITabletTool::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITablet::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITabletPad::getLibinputHandle() { return nullptr; } void Aquamarine::IKeyboard::updateLEDs(uint32_t leds) { ; } 07070100000048000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001A00000000aquamarine-0.3.3/src/misc07070100000049000081A400000000000000000000000166C38D3F000003D1000000000000000000000000000000000000002900000000aquamarine-0.3.3/src/misc/Attachment.cpp#include <aquamarine/misc/Attachment.hpp> using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer bool Aquamarine::CAttachmentManager::has(eAttachmentType type) { for (auto& a : attachments) { if (a->type() == type) return true; } return false; } SP<IAttachment> Aquamarine::CAttachmentManager::get(eAttachmentType type) { for (auto& a : attachments) { if (a->type() == type) return a; } return nullptr; } void Aquamarine::CAttachmentManager::add(SP<IAttachment> attachment) { attachments.emplace_back(attachment); } void Aquamarine::CAttachmentManager::remove(SP<IAttachment> attachment) { std::erase(attachments, attachment); } void Aquamarine::CAttachmentManager::removeByType(eAttachmentType type) { std::erase_if(attachments, [type](const auto& e) { return e->type() == type; }); } void Aquamarine::CAttachmentManager::clear() { attachments.clear(); } 0707010000004A000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001C00000000aquamarine-0.3.3/src/output0707010000004B000081A400000000000000000000000166C38D3F00000DBB000000000000000000000000000000000000002700000000aquamarine-0.3.3/src/output/Output.cpp#include <aquamarine/output/Output.hpp> using namespace Aquamarine; Hyprutils::Memory::CSharedPointer<SOutputMode> Aquamarine::IOutput::preferredMode() { for (auto& m : modes) { if (m->preferred) return m; } return nullptr; } void Aquamarine::IOutput::moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipShedule) { ; } bool Aquamarine::IOutput::setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::IOutput::setCursorVisible(bool visible) { ; } void Aquamarine::IOutput::scheduleFrame(const scheduleFrameReason reason) { ; } Hyprutils::Math::Vector2D Aquamarine::IOutput::cursorPlaneSize() { return {}; // error } size_t Aquamarine::IOutput::getGammaSize() { return 0; } bool Aquamarine::IOutput::destroy() { return false; } const Aquamarine::COutputState::SInternalState& Aquamarine::COutputState::state() { return internalState; } void Aquamarine::COutputState::addDamage(const Hyprutils::Math::CRegion& region) { internalState.damage.add(region); internalState.committed |= AQ_OUTPUT_STATE_DAMAGE; } void Aquamarine::COutputState::clearDamage() { internalState.damage.clear(); internalState.committed |= AQ_OUTPUT_STATE_DAMAGE; } void Aquamarine::COutputState::setEnabled(bool enabled) { internalState.enabled = enabled; internalState.committed |= AQ_OUTPUT_STATE_ENABLED; } void Aquamarine::COutputState::setAdaptiveSync(bool enabled) { internalState.adaptiveSync = enabled; internalState.committed |= AQ_OUTPUT_STATE_ADAPTIVE_SYNC; } void Aquamarine::COutputState::setPresentationMode(eOutputPresentationMode mode) { internalState.presentationMode = mode; internalState.committed |= AQ_OUTPUT_STATE_PRESENTATION_MODE; } void Aquamarine::COutputState::setGammaLut(const std::vector<uint16_t>& lut) { internalState.gammaLut = lut; internalState.committed |= AQ_OUTPUT_STATE_GAMMA_LUT; } void Aquamarine::COutputState::setMode(Hyprutils::Memory::CSharedPointer<SOutputMode> mode) { internalState.mode = mode; internalState.customMode = nullptr; internalState.committed |= AQ_OUTPUT_STATE_MODE; } void Aquamarine::COutputState::setCustomMode(Hyprutils::Memory::CSharedPointer<SOutputMode> mode) { internalState.mode.reset(); internalState.customMode = mode; internalState.committed |= AQ_OUTPUT_STATE_MODE; } void Aquamarine::COutputState::setFormat(uint32_t drmFormat) { internalState.drmFormat = drmFormat; internalState.committed |= AQ_OUTPUT_STATE_FORMAT; } void Aquamarine::COutputState::setBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer) { internalState.buffer = buffer; internalState.committed |= AQ_OUTPUT_STATE_BUFFER; } void Aquamarine::COutputState::setExplicitInFence(int64_t fenceFD) { internalState.explicitInFence = fenceFD; internalState.committed |= AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE; } void Aquamarine::COutputState::setExplicitOutFence(int64_t fenceFD) { // internalState.explicitOutFence = fenceFD; internalState.committed |= AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE; } void Aquamarine::COutputState::resetExplicitFences() { // fences are now used, let's reset them to not confuse ourselves later. internalState.explicitInFence = -1; internalState.explicitOutFence = -1; } void Aquamarine::COutputState::onCommit() { internalState.committed = 0; internalState.damage.clear(); } 0707010000004C000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001B00000000aquamarine-0.3.3/src/utils0707010000004D000081A400000000000000000000000166C38D3F0000010F000000000000000000000000000000000000002B00000000aquamarine-0.3.3/src/utils/FormatUtils.cpp#include "FormatUtils.hpp" #include <drm_fourcc.h> #include <xf86drm.h> #include <cstdlib> std::string fourccToName(uint32_t drmFormat) { auto fmt = drmGetFormatName(drmFormat); std::string name = fmt ? fmt : "unknown"; free(fmt); return name; } 0707010000004E000081A400000000000000000000000166C38D3F00000138000000000000000000000000000000000000002600000000aquamarine-0.3.3/src/utils/Shared.cpp#include "Shared.hpp" #include <cstdlib> bool Aquamarine::envEnabled(const std::string& env) { auto e = getenv(env.c_str()); return e && e == std::string{"1"}; } static bool trace = []() -> bool { return Aquamarine::envEnabled("AQ_TRACE"); }(); bool Aquamarine::isTrace() { return trace; } 0707010000004F000041ED00000000000000000000000266C38D3F00000000000000000000000000000000000000000000001700000000aquamarine-0.3.3/tests07070100000050000081A400000000000000000000000166C38D3F00000F25000000000000000000000000000000000000002800000000aquamarine-0.3.3/tests/SimpleWindow.cpp#include <aquamarine/backend/Backend.hpp> #include <aquamarine/output/Output.hpp> #include <aquamarine/input/Input.hpp> #include <iostream> #include <wayland-server.h> using namespace Hyprutils::Signal; using namespace Hyprutils::Memory; #define SP CSharedPointer static const char* aqLevelToString(Aquamarine::eBackendLogLevel level) { switch (level) { case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return "TRACE"; case Aquamarine::eBackendLogLevel::AQ_LOG_DEBUG: return "DEBUG"; case Aquamarine::eBackendLogLevel::AQ_LOG_ERROR: return "ERROR"; case Aquamarine::eBackendLogLevel::AQ_LOG_WARNING: return "WARNING"; case Aquamarine::eBackendLogLevel::AQ_LOG_CRITICAL: return "CRITICAL"; default: break; } return "UNKNOWN"; } void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { std::cout << "[AQ] [" << aqLevelToString(level) << "] " << msg << "\n"; } CHyprSignalListener newOutputListener, outputFrameListener, outputStateListener, mouseMotionListener, keyboardKeyListener, newMouseListener, newKeyboardListener; SP<Aquamarine::IOutput> output; // void onFrame() { std::cout << "[Client] onFrame\n"; auto buf = output->swapchain->next(nullptr); output->state->setBuffer(buf); output->commit(); } void onState(const Aquamarine::IOutput::SStateEvent& event) { std::cout << "[Client] onState with size " << std::format("{}", event.size) << "\n"; output->state->setEnabled(true); output->state->setCustomMode(makeShared<Aquamarine::SOutputMode>(Aquamarine::SOutputMode{.pixelSize = event.size})); output->state->setFormat(DRM_FORMAT_XRGB8888); output->commit(); } int main(int argc, char** argv, char** envp) { Aquamarine::SBackendOptions options; options.logFunction = aqLog; std::vector<Aquamarine::SBackendImplementationOptions> implementations; Aquamarine::SBackendImplementationOptions waylandOptions; waylandOptions.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; waylandOptions.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE; implementations.emplace_back(waylandOptions); auto aqBackend = Aquamarine::CBackend::create(implementations, options); newOutputListener = aqBackend->events.newOutput.registerListener([](std::any data) { output = std::any_cast<SP<Aquamarine::IOutput>>(data); std::cout << "[Client] Got a new output named " << output->name << "\n"; outputFrameListener = output->events.frame.registerListener([](std::any data) { onFrame(); }); outputStateListener = output->events.state.registerListener([](std::any data) { onState(std::any_cast<Aquamarine::IOutput::SStateEvent>(data)); }); }); newMouseListener = aqBackend->events.newPointer.registerListener([] (std::any pointer) { auto p = std::any_cast<SP<Aquamarine::IPointer>>(pointer); mouseMotionListener = p->events.warp.registerListener([] (std::any data) { auto e = std::any_cast<Aquamarine::IPointer::SWarpEvent>(data); std::cout << "[Client] Mouse warped to " << std::format("{}", e.absolute) << "\n"; }); }); newKeyboardListener = aqBackend->events.newKeyboard.registerListener([] (std::any keeb) { auto k = std::any_cast<SP<Aquamarine::IKeyboard>>(keeb); keyboardKeyListener = k->events.key.registerListener([] (std::any data) { auto e = std::any_cast<Aquamarine::IKeyboard::SKeyEvent>(data); std::cout << "[Client] Key " << std::format("{}", e.key) << " state: " << e.pressed << " \n"; }); }); if (!aqBackend || !aqBackend->start()) { std::cout << "Failed to start the aq backend\n"; return 1; } // FIXME: write an event loop. // aqBackend->enterLoop(); return 0; }07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!718 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor