pax_global_header00006660000000000000000000000064150674467310014527gustar00rootroot0000000000000052 comment=c48279d1e0f0a4399b5a2d56c16f2ec677ba18f8 hyprlock-0.9.2/000077500000000000000000000000001506744673100133725ustar00rootroot00000000000000hyprlock-0.9.2/.clang-format000066400000000000000000000034161506744673100157510ustar00rootroot00000000000000--- 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 hyprlock-0.9.2/.clang-tidy000066400000000000000000000072721506744673100154360ustar00rootroot00000000000000WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' FormatStyle: 'file' Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declaration-namespace, -bugprone-forward-declaration-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, -bugprone-assignment-in-if-condition, concurrency-*, -concurrency-mt-unsafe, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-init-variables, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, google-global-names-in-headers, -google-readability-casting, google-runtime-operator, misc-*, -misc-unused-parameters, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -misc-use-anonymous-namespace, -misc-const-correctness, modernize-*, -modernize-return-braced-init-list, -modernize-use-trailing-return-type, -modernize-use-using, -modernize-use-override, -modernize-avoid-c-arrays, -modernize-macro-to-enum, -modernize-loop-convert, -modernize-use-nodiscard, -modernize-pass-by-value, -modernize-use-auto, performance-*, -performance-avoid-endl, -performance-unnecessary-value-param, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-magic-numbers, -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, -readability-else-after-return, -readability-container-data-pointer, -readability-implicit-bool-conversion, -readability-avoid-nested-conditional-operator, -readability-redundant-member-init, -readability-redundant-string-init, -readability-avoid-const-params-in-decls, -readability-named-parameter, -readability-convert-member-functions-to-static, -readability-qualified-auto, -readability-make-member-function-const, -readability-isolate-declaration, -readability-inconsistent-declaration-parameter-name, -clang-diagnostic-error, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true performance-inefficient-string-concatenation.StrictMode: true readability-braces-around-statements.ShortStatementLines: 0 readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ClassIgnoredRegexp: I.* readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? readability-identifier-naming.EnumCase: CamelCase readability-identifier-naming.EnumPrefix: e readability-identifier-naming.EnumConstantCase: UPPER_CASE readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.NamespaceCase: CamelCase readability-identifier-naming.NamespacePrefix: N readability-identifier-naming.StructPrefix: S readability-identifier-naming.StructCase: CamelCase hyprlock-0.9.2/.github/000077500000000000000000000000001506744673100147325ustar00rootroot00000000000000hyprlock-0.9.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001506744673100171155ustar00rootroot00000000000000hyprlock-0.9.2/.github/ISSUE_TEMPLATE/bug.yml000066400000000000000000000055401506744673100204210ustar00rootroot00000000000000name: Bug Report description: Something is not working right labels: ["bug"] body: - type: markdown attributes: value: | ## Before opening a new issue, please take a moment to search through the current open and closed issues to check if it already exists. --- - type: dropdown id: type attributes: label: Regression? description: | Regression means that something used to work but no longer does. **BEFORE CONTINUING**, please check if this bug is a regression or not, and if it is, we need you to bisect with the help of the wiki: https://wiki.hyprland.org/Crashes-and-Bugs/#bisecting-an-issue multiple: true options: - "Yes" - "No" validations: required: true - type: textarea id: ver attributes: label: Hyprlock Info and Version description: | Please enter your hyprlock version and config. Use `hyprlock --version` if it is available (Introduced after v0.4.1). Otherwise please figure out the hyprlock version via your distribution. If you built it yourself, post the commit hash. Provide your hyprlock config. Usually `~/.config/hypr/hyprlock.conf`. value: "
Hyprlock config ```sh ```
" validations: required: true - type: textarea id: compositor attributes: label: Compositor Info and Version description: | If you are on hyprland, paste the output of `hyprctl systeminfo` here. If you are on another compositor, enter the compositor name and version you are on. value: "
System/Version info ```sh ```
" validations: required: true - type: textarea id: desc attributes: label: Description description: "What went wrong?" validations: required: true - type: textarea id: repro attributes: label: How to reproduce description: "How can someone else reproduce the issue?" validations: required: true - type: textarea id: logs attributes: label: Crash reports, logs, images, videos description: | Anything that can help. Please always ATTACH and not paste them. To get hyprlock logs, start it from the commandline. If you are using hypridle/swayidle as a systemd service, you can use `journalctl -b0 --user -u hypridle/swayidle` to get some logs. Compositor logs and crashes are sometimes relevant as well. Hyprland logs can be found in $XDG_RUNTIME_DIR/hypr. Hyprland crash reports are stored in ~/.cache/hyprland or $XDG_CACHE_HOME/hyprland. hyprlock-0.9.2/.github/ISSUE_TEMPLATE/feature.yml000066400000000000000000000006411506744673100212740ustar00rootroot00000000000000name: Feature Request description: I'd like to request additional functionality labels: ["enhancement"] body: - type: markdown attributes: value: | Before opening a new issue, take a moment to search through the current open ones. --- - type: textarea id: desc attributes: label: Description description: "Describe your idea" validations: required: true hyprlock-0.9.2/.github/workflows/000077500000000000000000000000001506744673100167675ustar00rootroot00000000000000hyprlock-0.9.2/.github/workflows/nix.yml000066400000000000000000000031071506744673100203110ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: nix_conf: | keep-env-derivations = true keep-outputs = true - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}- # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G # do purge caches purge: true # purge all versions of the cache purge-prefixes: nix-${{ runner.os }}- # created more than this number of seconds ago purge-created: 0 # or, last accessed more than this number of seconds ago # relative to the start of the `Post Restore and save Nix store` phase purge-last-accessed: 0 # except any version with the key that is the same as the `primary-key` purge-primary-key: never # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build run: nix flake check --print-build-logs --keep-going hyprlock-0.9.2/.gitignore000066400000000000000000000002541506744673100153630ustar00rootroot00000000000000**/.cache .direnv .envrc .vscode/ CMakeCache.txt CMakeFiles/ Makefile cmake_install.cmake build/ compile_commands.json protocols/*.cpp protocols/*.hpp *.kdev4 .gdb_history hyprlock-0.9.2/CMakeLists.txt000066400000000000000000000120121506744673100161260ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} VERSION) project( hyprlock DESCRIPTION "A gpu-accelerated screen lock for Hyprland" VERSION ${VERSION}) set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hyprlock in Debug with CMake") add_compile_definitions(HYPRLAND_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring hyprlock in Release with CMake") endif() include_directories(. "protocols/") include(GNUInstallDirs) # configure set(CMAKE_CXX_STANDARD 23) add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing) add_compile_definitions(HYPRLOCK_VERSION="${VERSION}") if (DEFINED HYPRLOCK_COMMIT) add_compile_definitions(HYPRLOCK_COMMIT="${HYPRLOCK_COMMIT}") else() # get git commit execute_process( OUTPUT_VARIABLE GIT_SHORT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND git rev-parse --short HEAD ) add_compile_definitions(HYPRLOCK_COMMIT="${GIT_SHORT_HASH}") endif() if (DEFINED HYPRLOCK_VERSION_COMMIT) add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${HYPRLOCK_VERSION_COMMIT}") else() # get git commit of v$VERSION execute_process( OUTPUT_VARIABLE GIT_TAG_SHORT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND git rev-parse --short v${VERSION} ) add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${GIT_TAG_SHORT_HASH}") endif() message(STATUS "VERSION COMMIT: ${HYPRLOCK_VERSION_COMMIT}") message(STATUS "COMMIT: ${HYPRLOCK_COMMIT}") # position independent build: __FILE__ add_compile_options(-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) # dependencies message(STATUS "Checking deps...") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED COMPONENTS EGL GLES3) find_package(hyprwayland-scanner 0.4.4 REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols>=1.35 wayland-egl hyprlang>=0.6.0 egl xkbcommon cairo pangocairo libdrm gbm hyprutils>=0.8.0 sdbus-c++>=2.0.0 hyprgraphics) find_library(PAM_FOUND pam) if(PAM_FOUND) message(STATUS "Found pam") set(PAM_LIB ${PAM_FOUND}) else() pkg_check_modules(PAM IMPORTED_TARGET pam) if(PAM_FOUND) set(PAM_LIB PkgConfig::PAM) else() message(FATAL_ERROR "The required library libpam was not found.") endif() endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(hyprlock ${SRCFILES}) target_link_libraries(hyprlock PRIVATE ${PAM_LIB} rt Threads::Threads PkgConfig::deps OpenGL::EGL OpenGL::GLES3) # 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(hyprlock 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(hyprlock PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so # the dir won't be there protocolwayland() protocolnew("protocols" "wlr-screencopy-unstable-v1" true) protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolnew("staging/fractional-scale" "fractional-scale-v1" false) protocolnew("stable/viewporter" "viewporter" false) protocolnew("staging/cursor-shape" "cursor-shape-v1" false) protocolnew("stable/tablet" "tablet-v2" false) # Installation install(TARGETS hyprlock) install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d) install( FILES ${CMAKE_SOURCE_DIR}/assets/example.conf DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr RENAME hyprlock.conf) hyprlock-0.9.2/LICENSE000066400000000000000000000027371506744673100144100ustar00rootroot00000000000000BSD 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. hyprlock-0.9.2/README.md000066400000000000000000000027071506744673100146570ustar00rootroot00000000000000# hyprlock Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility. ## Features - Uses the ext-session-lock protocol - Support for fractional-scale - Fully GPU accelerated - Multi-threaded resource acquisition - Blurred screenshot as the background - Native fingerprint support (using libfprint's dbus interface) - Some of Hyprland's eyecandy: gradient borders, blur, animations, shadows, etc. - and more... ## How it looks ![](https://i.ibb.co/8Bd98BP/20240220-00h12m46s.png) ## Docs / Configuration [See the wiki](https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/) ## Arch install ```sh pacman -S hyprlock # binary x86 tagged release # or yay -S hyprlock-git # compiles from latest source ``` ## Building ### Deps You need the following dependencies - cairo - hyprgraphics - hyprland-protocols - hyprlang - hyprutils - hyprwayland-scanner - mesa (required is libgbm, libdrm and the opengl runtime) - pam - pango - sdbus-cpp (>= 2.0.0) - wayland-client - wayland-protocols - xkbcommon Sometimes distro packages are missing required development files. Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`. ### Building Building: ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build cmake --build ./build --config Release --target hyprlock -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` ``` Installation: ```sh sudo cmake --install build ``` hyprlock-0.9.2/VERSION000066400000000000000000000000061506744673100144360ustar00rootroot000000000000000.9.2 hyprlock-0.9.2/assets/000077500000000000000000000000001506744673100146745ustar00rootroot00000000000000hyprlock-0.9.2/assets/example.conf000066400000000000000000000045761506744673100172120ustar00rootroot00000000000000# sample hyprlock.conf # for more configuration options, refer https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock # # rendered text in all widgets supports pango markup (e.g. or tags) # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#general-remarks # # shortcuts to clear password buffer: ESC, Ctrl+U, Ctrl+Backspace # # you can get started by copying this config to ~/.config/hypr/hyprlock.conf # $font = Monospace general { hide_cursor = false } # uncomment to enable fingerprint authentication # auth { # fingerprint { # enabled = true # ready_message = Scan fingerprint to unlock # present_message = Scanning... # retry_delay = 250 # in milliseconds # } # } animations { enabled = true bezier = linear, 1, 1, 0, 0 animation = fadeIn, 1, 5, linear animation = fadeOut, 1, 5, linear animation = inputFieldDots, 1, 2, linear } background { monitor = path = screenshot blur_passes = 3 } input-field { monitor = size = 20%, 5% outline_thickness = 3 inner_color = rgba(0, 0, 0, 0.0) # no fill outer_color = rgba(33ccffee) rgba(00ff99ee) 45deg check_color = rgba(00ff99ee) rgba(ff6633ee) 120deg fail_color = rgba(ff6633ee) rgba(ff0066ee) 40deg font_color = rgb(143, 143, 143) fade_on_empty = false rounding = 15 font_family = $font placeholder_text = Input password... fail_text = $PAMFAIL # uncomment to use a letter instead of a dot to indicate the typed password # dots_text_format = * # dots_size = 0.4 dots_spacing = 0.3 # uncomment to use an input indicator that does not show the password length (similar to swaylock's input indicator) # hide_input = true position = 0, -20 halign = center valign = center } # TIME label { monitor = text = $TIME # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#variable-substitution font_size = 90 font_family = $font position = -30, 0 halign = right valign = top } # DATE label { monitor = text = cmd[update:60000] date +"%A, %d %B %Y" # update every 60 seconds font_size = 25 font_family = $font position = -30, -150 halign = right valign = top } label { monitor = text = $LAYOUT[en,ru] font_size = 24 onclick = hyprctl switchxkblayout all next position = 250, -20 halign = center valign = center } hyprlock-0.9.2/flake.lock000066400000000000000000000066551506744673100153420ustar00rootroot00000000000000{ "nodes": { "hyprgraphics": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1750621377, "narHash": "sha256-8u6b5oAdX0rCuoR8wFenajBRmI+mzbpNig6hSCuWUzE=", "owner": "hyprwm", "repo": "hyprgraphics", "rev": "b3d628d01693fb9bb0a6690cd4e7b80abda04310", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprgraphics", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1750371198, "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", "owner": "hyprwm", "repo": "hyprlang", "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1751061882, "narHash": "sha256-g9n8Vrbx+2JYM170P9BbvGHN39Wlkr4U+V2WLHQsXL8=", "owner": "hyprwm", "repo": "hyprutils", "rev": "4737241eaf8a1e51671a2a088518071f9a265cf4", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1750371869, "narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1751011381, "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", "owner": "NixOS", "repo": "nixpkgs", "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprgraphics": "hyprgraphics", "hyprlang": "hyprlang", "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 } hyprlock-0.9.2/flake.nix000066400000000000000000000033641506744673100152020ustar00rootroot00000000000000{ description = "Hyprland's GPU-accelerated screen locking utility"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprgraphics = { url = "github:hyprwm/hyprgraphics"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprlang = { url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; 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; [default]; }); in { overlays = import ./nix/overlays.nix {inherit inputs lib self;}; packages = eachSystem (system: { default = self.packages.${system}.hyprlock; inherit (pkgsFor.${system}) hyprlock; }); homeManagerModules = { default = self.homeManagerModules.hyprlock; hyprlock = builtins.throw "hyprlock: the flake HM module has been removed. Use the module from Home Manager upstream."; }; checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } hyprlock-0.9.2/nix/000077500000000000000000000000001506744673100141705ustar00rootroot00000000000000hyprlock-0.9.2/nix/default.nix000066400000000000000000000021361506744673100163360ustar00rootroot00000000000000{ lib, stdenv, cmake, pkg-config, cairo, libdrm, libGL, libxkbcommon, libgbm, hyprgraphics, hyprlang, hyprutils, hyprwayland-scanner, pam, pango, sdbus-cpp, systemdLibs, wayland, wayland-protocols, wayland-scanner, version ? "git", shortRev ? "", }: stdenv.mkDerivation { pname = "hyprlock"; inherit version; src = ../.; nativeBuildInputs = [ cmake pkg-config hyprwayland-scanner wayland-scanner ]; buildInputs = [ cairo libdrm libGL libxkbcommon libgbm hyprgraphics hyprlang hyprutils pam pango sdbus-cpp systemdLibs wayland wayland-protocols ]; cmakeFlags = lib.mapAttrsToList lib.cmakeFeature { HYPRLOCK_COMMIT = shortRev; HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit) }; meta = { homepage = "https://github.com/hyprwm/hyprlock"; description = "A gpu-accelerated screen lock for Hyprland"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; mainProgram = "hyprlock"; }; } hyprlock-0.9.2/nix/overlays.nix000066400000000000000000000023551506744673100165610ustar00rootroot00000000000000{ lib, inputs, self, }: let mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); in { default = inputs.self.overlays.hyprlock; hyprlock = lib.composeManyExtensions [ inputs.hyprgraphics.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default inputs.self.overlays.sdbuscpp (final: prev: { hyprlock = prev.callPackage ./default.nix { stdenv = prev.gcc15Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); inherit (final) hyprlang; shortRev = self.sourceInfo.shortRev or "dirty"; }; }) ]; sdbuscpp = final: prev: { sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: { version = "2.0.0"; src = final.fetchFromGitHub { owner = "Kistler-group"; repo = "sdbus-cpp"; rev = "refs/tags/v${self.version}"; hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g="; }; }); }; } hyprlock-0.9.2/pam/000077500000000000000000000000001506744673100141475ustar00rootroot00000000000000hyprlock-0.9.2/pam/hyprlock000066400000000000000000000001761506744673100157310ustar00rootroot00000000000000# PAM configuration file for hyprlock # the 'login' configuration file (see /etc/pam.d/login) auth include login hyprlock-0.9.2/protocols/000077500000000000000000000000001506744673100154165ustar00rootroot00000000000000hyprlock-0.9.2/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000226051506744673100232600ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. hyprlock-0.9.2/src/000077500000000000000000000000001506744673100141615ustar00rootroot00000000000000hyprlock-0.9.2/src/auth/000077500000000000000000000000001506744673100151225ustar00rootroot00000000000000hyprlock-0.9.2/src/auth/Auth.cpp000066400000000000000000000066371506744673100165430ustar00rootroot00000000000000#include "Auth.hpp" #include "Pam.hpp" #include "Fingerprint.hpp" #include "../config/ConfigManager.hpp" #include "../core/hyprlock.hpp" #include "src/helpers/Log.hpp" #include #include CAuth::CAuth() { static const auto ENABLEPAM = g_pConfigManager->getValue("auth:pam:enabled"); if (*ENABLEPAM) m_vImpls.emplace_back(makeShared()); static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue("auth:fingerprint:enabled"); if (*ENABLEFINGERPRINT) m_vImpls.emplace_back(makeShared()); RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!"); } void CAuth::start() { for (const auto& i : m_vImpls) { i->init(); } } void CAuth::submitInput(const std::string& input) { for (const auto& i : m_vImpls) { i->handleInput(input); } g_pHyprlock->clearPasswordBuffer(); } bool CAuth::checkWaiting() { return std::ranges::any_of(m_vImpls, [](const auto& i) { return i->checkWaiting(); }); } const std::string& CAuth::getCurrentFailText() { return m_sCurrentFail.failText; } std::optional CAuth::getFailText(eAuthImplementations implType) { for (const auto& i : m_vImpls) { if (i->getImplType() == implType) return i->getLastFailText(); } return std::nullopt; } std::optional CAuth::getPrompt(eAuthImplementations implType) { for (const auto& i : m_vImpls) { if (i->getImplType() == implType) return i->getLastPrompt(); } return std::nullopt; } size_t CAuth::getFailedAttempts() { return m_sCurrentFail.failedAttempts; } SP CAuth::getImpl(eAuthImplementations implType) { for (const auto& i : m_vImpls) { if (i->getImplType() == implType) return i; } return nullptr; } void CAuth::terminate() { for (const auto& i : m_vImpls) { i->terminate(); } } static void unlockCallback(ASP self, void* data) { g_pHyprlock->unlock(); } void CAuth::enqueueUnlock() { g_pHyprlock->addTimer(std::chrono::milliseconds(0), unlockCallback, nullptr); } static void passwordFailCallback(ASP self, void* data) { g_pAuth->m_bDisplayFailText = true; g_pHyprlock->enqueueForceUpdateTimers(); g_pHyprlock->renderAllOutputs(); } static void displayFailTimeoutCallback(ASP self, void* data) { if (g_pAuth->m_bDisplayFailText) { g_pAuth->m_bDisplayFailText = false; g_pHyprlock->renderAllOutputs(); } } void CAuth::enqueueFail(const std::string& failText, eAuthImplementations implType) { static const auto FAILTIMEOUT = g_pConfigManager->getValue("general:fail_timeout"); m_sCurrentFail.failText = failText; m_sCurrentFail.failSource = implType; m_sCurrentFail.failedAttempts++; Debug::log(LOG, "Failed attempts: {}", m_sCurrentFail.failedAttempts); if (m_resetDisplayFailTimer) { m_resetDisplayFailTimer->cancel(); m_resetDisplayFailTimer.reset(); } g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordFailCallback, nullptr); m_resetDisplayFailTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(*FAILTIMEOUT), displayFailTimeoutCallback, nullptr); } void CAuth::resetDisplayFail() { g_pAuth->m_bDisplayFailText = false; m_resetDisplayFailTimer->cancel(); m_resetDisplayFailTimer.reset(); } hyprlock-0.9.2/src/auth/Auth.hpp000066400000000000000000000040671506744673100165430ustar00rootroot00000000000000#pragma once #include #include #include "../defines.hpp" #include "../core/Timer.hpp" enum eAuthImplementations { AUTH_IMPL_PAM = 0, AUTH_IMPL_FINGERPRINT = 1, }; class IAuthImplementation { public: virtual ~IAuthImplementation() = default; virtual eAuthImplementations getImplType() = 0; virtual void init() = 0; virtual void handleInput(const std::string& input) = 0; virtual bool checkWaiting() = 0; virtual std::optional getLastFailText() = 0; virtual std::optional getLastPrompt() = 0; virtual void terminate() = 0; friend class CAuth; }; class CAuth { public: CAuth(); void start(); void submitInput(const std::string& input); bool checkWaiting(); const std::string& getCurrentFailText(); std::optional getFailText(eAuthImplementations implType); std::optional getPrompt(eAuthImplementations implType); size_t getFailedAttempts(); SP getImpl(eAuthImplementations implType); void terminate(); void enqueueUnlock(); void enqueueFail(const std::string& failText, eAuthImplementations implType); void resetDisplayFail(); // Should only be set via the main thread bool m_bDisplayFailText = false; private: struct { std::string failText = ""; eAuthImplementations failSource = AUTH_IMPL_PAM; size_t failedAttempts = 0; } m_sCurrentFail; std::vector> m_vImpls; ASP m_resetDisplayFailTimer; }; inline UP g_pAuth; hyprlock-0.9.2/src/auth/Fingerprint.cpp000066400000000000000000000243241506744673100201220ustar00rootroot00000000000000#include "Fingerprint.hpp" #include "../core/hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include #include #include #include static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"}; static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"}; static const auto MANAGER = sdbus::ServiceName{"net.reactivated.Fprint.Manager"}; static const auto LOGIN_MANAGER = sdbus::ServiceName{"org.freedesktop.login1.Manager"}; enum MatchResult { MATCH_INVALID = 0, MATCH_NO_MATCH, MATCH_MATCHED, MATCH_RETRY, MATCH_SWIPE_TOO_SHORT, MATCH_FINGER_NOT_CENTERED, MATCH_REMOVE_AND_RETRY, MATCH_DISCONNECTED, MATCH_UNKNOWN_ERROR, }; static std::map s_mapStringToTestType = {{"verify-no-match", MATCH_NO_MATCH}, {"verify-match", MATCH_MATCHED}, {"verify-retry-scan", MATCH_RETRY}, {"verify-swipe-too-short", MATCH_SWIPE_TOO_SHORT}, {"verify-finger-not-centered", MATCH_FINGER_NOT_CENTERED}, {"verify-remove-and-retry", MATCH_REMOVE_AND_RETRY}, {"verify-disconnected", MATCH_DISCONNECTED}, {"verify-unknown-error", MATCH_UNKNOWN_ERROR}}; CFingerprint::CFingerprint() { static const auto FINGERPRINTREADY = g_pConfigManager->getValue("auth:fingerprint:ready_message"); m_sFingerprintReady = *FINGERPRINTREADY; static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue("auth:fingerprint:present_message"); m_sFingerprintPresent = *FINGERPRINTPRESENT; } CFingerprint::~CFingerprint() { ; } void CFingerprint::init() { try { m_sDBUSState.connection = sdbus::createSystemBusConnection(); m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"}); } catch (sdbus::Error& e) { Debug::log(ERR, "fprint: Failed to setup dbus ({})", e.what()); m_sDBUSState.connection.reset(); return; } m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional e, sdbus::Variant preparingForSleep) { if (e) { Debug::log(WARN, "fprint: Failed getting value for PreparingForSleep: {}", e->what()); return; } m_sDBUSState.sleeping = preparingForSleep.get(); // When entering sleep, the wake signal will trigger startVerify(). if (m_sDBUSState.sleeping) return; startVerify(); }); m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) { Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start); m_sDBUSState.sleeping = start; if (!m_sDBUSState.sleeping && !m_sDBUSState.verifying) startVerify(); }); } void CFingerprint::handleInput(const std::string& input) { ; } std::optional CFingerprint::getLastFailText() { if (!m_sFailureReason.empty()) return std::optional(m_sFailureReason); return std::nullopt; } std::optional CFingerprint::getLastPrompt() { if (!m_sPrompt.empty()) return std::optional(m_sPrompt); return std::nullopt; } bool CFingerprint::checkWaiting() { return false; } void CFingerprint::terminate() { if (!m_sDBUSState.abort) releaseDevice(); } std::shared_ptr CFingerprint::getConnection() { return m_sDBUSState.connection; } bool CFingerprint::createDeviceProxy() { auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"}); sdbus::ObjectPath path; try { proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path); } catch (sdbus::Error& e) { Debug::log(WARN, "fprint: couldn't connect to Fprint service ({})", e.what()); return false; } Debug::log(LOG, "fprint: using device path {}", path.c_str()); m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, path); m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "fprint: finger selected: {}", finger); }); m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); }); m_sDBUSState.device->uponSignal("PropertiesChanged") .onInterface("org.freedesktop.DBus.Properties") .call([this](const std::string& interface, const std::map& properties) { if (interface != DEVICE || m_sDBUSState.done) return; try { const auto presentVariant = properties.at("finger-present"); bool isPresent = presentVariant.get(); if (!isPresent) return; m_sPrompt = m_sFingerprintPresent; g_pHyprlock->enqueueForceUpdateTimers(); } catch (std::out_of_range& e) {} }); return true; } void CFingerprint::handleVerifyStatus(const std::string& result, bool done) { Debug::log(LOG, "fprint: handling status {}", result); auto matchResult = s_mapStringToTestType[result]; bool authenticated = false; bool retry = false; if (m_sDBUSState.sleeping) { stopVerify(); Debug::log(LOG, "fprint: device suspended"); return; } switch (matchResult) { case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break; case MATCH_NO_MATCH: stopVerify(); if (m_sDBUSState.retries >= 3) { m_sFailureReason = "Fingerprint auth disabled (too many failed attempts)"; } else { done = false; static const auto RETRYDELAY = g_pConfigManager->getValue("auth:fingerprint:retry_delay"); g_pHyprlock->addTimer(std::chrono::milliseconds(*RETRYDELAY), [](ASP self, void* data) { ((CFingerprint*)data)->startVerify(true); }, this); m_sFailureReason = "Fingerprint did not match"; } break; case MATCH_UNKNOWN_ERROR: stopVerify(); m_sFailureReason = "Fingerprint auth disabled (unknown error)"; break; case MATCH_MATCHED: stopVerify(); authenticated = true; g_pAuth->enqueueUnlock(); break; case MATCH_RETRY: retry = true; m_sPrompt = "Please retry fingerprint scan"; break; case MATCH_SWIPE_TOO_SHORT: retry = true; m_sPrompt = "Swipe too short - try again"; break; case MATCH_FINGER_NOT_CENTERED: retry = true; m_sPrompt = "Finger not centered - try again"; break; case MATCH_REMOVE_AND_RETRY: retry = true; m_sPrompt = "Remove your finger and try again"; break; case MATCH_DISCONNECTED: m_sFailureReason = "Fingerprint device disconnected"; m_sDBUSState.abort = true; break; } if (!authenticated && !retry) g_pAuth->enqueueFail(m_sFailureReason, AUTH_IMPL_FINGERPRINT); else if (retry) g_pHyprlock->enqueueForceUpdateTimers(); if (done || m_sDBUSState.abort) m_sDBUSState.done = true; } void CFingerprint::claimDevice() { const auto currentUser = ""; // Empty string means use the caller's id. m_sDBUSState.device->callMethodAsync("Claim").onInterface(DEVICE).withArguments(currentUser).uponReplyInvoke([this](std::optional e) { if (e) Debug::log(WARN, "fprint: could not claim device, {}", e->what()); else { Debug::log(LOG, "fprint: claimed device"); startVerify(); } }); } void CFingerprint::startVerify(bool isRetry) { m_sDBUSState.verifying = true; if (!m_sDBUSState.device) { if (!createDeviceProxy()) return; claimDevice(); return; } auto finger = "any"; // Any finger. m_sDBUSState.device->callMethodAsync("VerifyStart").onInterface(DEVICE).withArguments(finger).uponReplyInvoke([this, isRetry](std::optional e) { if (e) { Debug::log(WARN, "fprint: could not start verifying, {}", e->what()); if (isRetry) m_sFailureReason = "Fingerprint auth disabled (failed to restart)"; } else { Debug::log(LOG, "fprint: started verifying"); if (isRetry) { m_sDBUSState.retries++; m_sPrompt = "Could not match fingerprint. Try again."; } else m_sPrompt = m_sFingerprintReady; } g_pHyprlock->enqueueForceUpdateTimers(); }); } bool CFingerprint::stopVerify() { m_sDBUSState.verifying = false; if (!m_sDBUSState.device) return false; try { m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE); } catch (sdbus::Error& e) { Debug::log(WARN, "fprint: could not stop verifying, {}", e.what()); return false; } Debug::log(LOG, "fprint: stopped verification"); return true; } bool CFingerprint::releaseDevice() { if (!m_sDBUSState.device) return false; try { m_sDBUSState.device->callMethod("Release").onInterface(DEVICE); } catch (sdbus::Error& e) { Debug::log(WARN, "fprint: could not release device, {}", e.what()); return false; } Debug::log(LOG, "fprint: released device"); return true; } hyprlock-0.9.2/src/auth/Fingerprint.hpp000066400000000000000000000032171506744673100201250ustar00rootroot00000000000000#pragma once #include "Auth.hpp" #include #include #include #include class CFingerprint : public IAuthImplementation { public: CFingerprint(); virtual ~CFingerprint(); virtual eAuthImplementations getImplType() { return AUTH_IMPL_FINGERPRINT; } virtual void init(); virtual void handleInput(const std::string& input); virtual bool checkWaiting(); virtual std::optional getLastFailText(); virtual std::optional getLastPrompt(); virtual void terminate(); std::shared_ptr getConnection(); private: struct SDBUSState { std::shared_ptr connection; std::unique_ptr login; std::unique_ptr device; bool abort = false; bool done = false; int retries = 0; bool sleeping = false; bool verifying = false; } m_sDBUSState; std::string m_sFingerprintReady; std::string m_sFingerprintPresent; std::string m_sPrompt{""}; std::string m_sFailureReason{""}; void handleVerifyStatus(const std::string& result, const bool done); bool createDeviceProxy(); void claimDevice(); void startVerify(bool isRetry = false); bool stopVerify(); bool releaseDevice(); }; hyprlock-0.9.2/src/auth/Pam.cpp000066400000000000000000000147771506744673100163630ustar00rootroot00000000000000#include "Pam.hpp" #include "../core/hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include #include #include #include #if __has_include() #include #endif #include #include int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { const auto CONVERSATIONSTATE = (CPam::SPamConversationState*)appdata_ptr; struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); bool initialPrompt = true; for (int i = 0; i < num_msg; ++i) { switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: { const auto PROMPT = std::string(msg[i]->msg); const auto PROMPTCHANGED = PROMPT != CONVERSATIONSTATE->prompt; Debug::log(LOG, "PAM_PROMPT: {}", PROMPT); if (PROMPTCHANGED) g_pHyprlock->enqueueForceUpdateTimers(); // Some pam configurations ask for the password twice for whatever reason (Fedora su for example) // When the prompt is the same as the last one, I guess our answer can be the same. if (!initialPrompt && PROMPTCHANGED) { CONVERSATIONSTATE->prompt = PROMPT; CONVERSATIONSTATE->waitForInput(); } // Needed for unlocks via SIGUSR1 if (g_pHyprlock->isUnlocked()) return PAM_CONV_ERR; pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str()); initialPrompt = false; } break; case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break; case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); // Targets this log from pam_faillock: https://github.com/linux-pam/linux-pam/blob/fa3295e079dbbc241906f29bde5fb71bc4172771/modules/pam_faillock/pam_faillock.c#L417 if (const auto MSG = std::string(msg[i]->msg); MSG.contains("left to unlock")) { CONVERSATIONSTATE->failText = MSG; CONVERSATIONSTATE->failTextFromPam = true; } break; } } *resp = pamReply; return PAM_SUCCESS; } CPam::CPam() { static const auto PAMMODULE = g_pConfigManager->getValue("auth:pam:module"); m_sPamModule = *PAMMODULE; if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) { Debug::log(ERR, R"(Pam module "/etc/pam.d/{}" does not exist! Falling back to "/etc/pam.d/su")", m_sPamModule); m_sPamModule = "su"; } m_sConversationState.waitForInput = [this]() { this->waitForInput(); }; } CPam::~CPam() { ; } void CPam::init() { m_thread = std::thread([this]() { while (true) { resetConversation(); // Initial input m_sConversationState.prompt = "Password: "; waitForInput(); // For grace or SIGUSR1 unlocks if (g_pHyprlock->isUnlocked()) return; const auto AUTHENTICATED = auth(); // For SIGUSR1 unlocks if (g_pHyprlock->isUnlocked()) return; if (!AUTHENTICATED) g_pAuth->enqueueFail(m_sConversationState.failText, AUTH_IMPL_PAM); else { g_pAuth->enqueueUnlock(); return; } } }); } bool CPam::auth() { const pam_conv localConv = {.conv = conv, .appdata_ptr = (void*)&m_sConversationState}; pam_handle_t* handle = nullptr; auto uidPassword = getpwuid(getuid()); RASSERT(uidPassword && uidPassword->pw_name, "Failed to get username (getpwuid)"); int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle); if (ret != PAM_SUCCESS) { m_sConversationState.failText = "pam_start failed"; Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule); return false; } ret = pam_authenticate(handle, 0); pam_end(handle, ret); handle = nullptr; m_sConversationState.waitingForPamAuth = false; if (ret != PAM_SUCCESS) { if (!m_sConversationState.failTextFromPam) m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule); return false; } m_sConversationState.failText = "Successfully authenticated"; Debug::log(LOG, "auth: authenticated for {}", m_sPamModule); return true; } void CPam::waitForInput() { std::unique_lock lk(m_sConversationState.inputMutex); m_bBlockInput = false; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = true; m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return !m_sConversationState.inputRequested || g_pHyprlock->m_bTerminate; }); m_bBlockInput = true; } void CPam::handleInput(const std::string& input) { std::unique_lock lk(m_sConversationState.inputMutex); if (!m_sConversationState.inputRequested) Debug::log(ERR, "SubmitInput called, but the auth thread is not waiting for input!"); m_sConversationState.input = input; m_sConversationState.inputRequested = false; m_sConversationState.waitingForPamAuth = true; m_sConversationState.inputSubmittedCondition.notify_all(); } std::optional CPam::getLastFailText() { return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText); } std::optional CPam::getLastPrompt() { return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt); } bool CPam::checkWaiting() { return m_bBlockInput || m_sConversationState.waitingForPamAuth; } void CPam::terminate() { m_sConversationState.inputSubmittedCondition.notify_all(); if (m_thread.joinable()) m_thread.join(); } void CPam::resetConversation() { m_sConversationState.input = ""; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = false; m_sConversationState.failTextFromPam = false; } hyprlock-0.9.2/src/auth/Pam.hpp000066400000000000000000000027341506744673100163560ustar00rootroot00000000000000#pragma once #include "Auth.hpp" #include #include #include #include #include #include class CPam : public IAuthImplementation { public: struct SPamConversationState { std::string input = ""; std::string prompt = ""; std::string failText = ""; std::mutex inputMutex; std::condition_variable inputSubmittedCondition; bool waitingForPamAuth = false; bool inputRequested = false; bool failTextFromPam = false; std::function waitForInput = []() {}; }; CPam(); void waitForInput(); virtual ~CPam(); virtual eAuthImplementations getImplType() { return AUTH_IMPL_PAM; } virtual void init(); virtual void handleInput(const std::string& input); virtual bool checkWaiting(); virtual std::optional getLastFailText(); virtual std::optional getLastPrompt(); virtual void terminate(); private: std::thread m_thread; SPamConversationState m_sConversationState; bool m_bBlockInput = true; std::string m_sPamModule; bool auth(); void resetConversation(); }; hyprlock-0.9.2/src/config/000077500000000000000000000000001506744673100154265ustar00rootroot00000000000000hyprlock-0.9.2/src/config/ConfigDataValues.hpp000066400000000000000000000072451506744673100213260ustar00rootroot00000000000000#pragma once #include "../helpers/Log.hpp" #include "../helpers/Color.hpp" #include #include #include #include #include #include using namespace Hyprutils::String; enum eConfigValueDataTypes { CVD_TYPE_INVALID = -1, CVD_TYPE_LAYOUT = 0, CVD_TYPE_GRADIENT = 1, }; class ICustomConfigValueData { public: virtual ~ICustomConfigValueData() = 0; virtual eConfigValueDataTypes getDataType() = 0; virtual std::string toString() = 0; }; class CLayoutValueData : public ICustomConfigValueData { public: CLayoutValueData() = default; virtual ~CLayoutValueData() {}; virtual eConfigValueDataTypes getDataType() { return CVD_TYPE_LAYOUT; } virtual std::string toString() { return std::format("{}{},{}{}", m_vValues.x, (m_sIsRelative.x) ? "%" : "px", m_vValues.y, (m_sIsRelative.y) ? "%" : "px"); } static CLayoutValueData* fromAnyPv(const std::any& v) { RASSERT(v.type() == typeid(void*), "Invalid config value type"); const auto P = (CLayoutValueData*)std::any_cast(v); RASSERT(P, "Empty config value"); return P; } Hyprutils::Math::Vector2D getAbsolute(const Hyprutils::Math::Vector2D& viewport) { return { (m_sIsRelative.x ? (m_vValues.x / 100) * viewport.x : m_vValues.x), (m_sIsRelative.y ? (m_vValues.y / 100) * viewport.y : m_vValues.y), }; } Hyprutils::Math::Vector2D m_vValues; struct { bool x = false; bool y = false; } m_sIsRelative; }; class CGradientValueData : public ICustomConfigValueData { public: CGradientValueData() {}; CGradientValueData(CHyprColor col) { m_vColors.push_back(col); updateColorsOk(); }; virtual ~CGradientValueData() {}; virtual eConfigValueDataTypes getDataType() { return CVD_TYPE_GRADIENT; } void reset(CHyprColor col) { m_vColors.clear(); m_vColors.emplace_back(col); m_fAngle = 0; updateColorsOk(); } void updateColorsOk() { m_vColorsOkLabA.clear(); for (auto& c : m_vColors) { const auto OKLAB = c.asOkLab(); m_vColorsOkLabA.emplace_back(OKLAB.l); m_vColorsOkLabA.emplace_back(OKLAB.a); m_vColorsOkLabA.emplace_back(OKLAB.b); m_vColorsOkLabA.emplace_back(c.a); } } /* Vector containing the colors */ std::vector m_vColors; /* Vector containing pure colors for shoving into opengl */ std::vector m_vColorsOkLabA; /* Float corresponding to the angle (rad) */ float m_fAngle = 0; /* Whether this gradient stores a fallback value (not exlicitly set) */ bool m_bIsFallback = false; // bool operator==(const CGradientValueData& other) const { if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle) return false; for (size_t i = 0; i < m_vColors.size(); ++i) if (m_vColors[i] != other.m_vColors[i]) return false; return true; } virtual std::string toString() { std::string result; for (auto& c : m_vColors) { result += std::format("{:x} ", c.getAsHex()); } result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI)); return result; } static CGradientValueData* fromAnyPv(const std::any& v) { RASSERT(v.type() == typeid(void*), "Invalid config value type"); const auto P = (CGradientValueData*)std::any_cast(v); RASSERT(P, "Empty config value"); return P; } }; hyprlock-0.9.2/src/config/ConfigManager.cpp000066400000000000000000000765151506744673100206500ustar00rootroot00000000000000#include "ConfigManager.hpp" #include "ConfigDataValues.hpp" #include "../helpers/MiscFunctions.hpp" #include "../helpers/Log.hpp" #include "../core/AnimationManager.hpp" #include #include #include #include #include #include #include using namespace Hyprutils::String; using namespace Hyprutils::Animation; ICustomConfigValueData::~ICustomConfigValueData() { ; // empty } static Hyprlang::CParseResult handleSource(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; const auto RESULT = g_pConfigManager->handleSource(COMMAND, VALUE); Hyprlang::CParseResult result; if (RESULT.has_value()) result.setError(RESULT.value().c_str()); return result; } static Hyprlang::CParseResult handleBezier(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE); Hyprlang::CParseResult result; if (RESULT.has_value()) result.setError(RESULT.value().c_str()); return result; } static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE); Hyprlang::CParseResult result; if (RESULT.has_value()) result.setError(RESULT.value().c_str()); return result; } static Hyprlang::CParseResult configHandleLayoutOption(const char* v, void** data) { const std::string VALUE = v; Hyprlang::CParseResult result; if (!*data) *data = new CLayoutValueData(); const auto DATA = (CLayoutValueData*)(*data); const auto SPLIT = VALUE.find(','); if (SPLIT == std::string::npos) { result.setError(std::format("expected two comma seperated values, got {}", VALUE).c_str()); return result; } auto lhs = VALUE.substr(0, SPLIT); auto rhs = VALUE.substr(SPLIT + 1); if (rhs.starts_with(" ")) rhs = rhs.substr(1); if (lhs.contains(",") || rhs.contains(",")) { result.setError(std::format("too many arguments in {}", VALUE).c_str()); return result; } if (lhs.ends_with("%")) { DATA->m_sIsRelative.x = true; lhs.pop_back(); } if (rhs.ends_with("%")) { DATA->m_sIsRelative.y = true; rhs.pop_back(); } DATA->m_vValues = Hyprutils::Math::Vector2D{std::stof(lhs), std::stof(rhs)}; return result; } static void configHandleLayoutOptionDestroy(void** data) { if (*data) delete reinterpret_cast(*data); } static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) { const std::string V = VALUE; if (!*data) *data = new CGradientValueData(); const auto DATA = reinterpret_cast(*data); DATA->m_vColors.clear(); DATA->m_bIsFallback = false; std::string parseError = ""; std::string rolling = V; while (!rolling.empty()) { const auto SPACEPOS = rolling.find(' '); const bool LAST = SPACEPOS == std::string::npos; std::string var = rolling.substr(0, SPACEPOS); if (var.find("rgb") != std::string::npos) { // rgb(a) const auto CLOSEPARENPOS = rolling.find(')'); if (CLOSEPARENPOS == std::string::npos || CLOSEPARENPOS + 1 >= rolling.length()) { var = trim(rolling); rolling.clear(); } else { var = rolling.substr(0, CLOSEPARENPOS + 1); rolling = trim(rolling.substr(CLOSEPARENPOS + 2)); } } else if (var.find("deg") != std::string::npos) { // last arg try { DATA->m_fAngle = std::stoi(var.substr(0, var.find("deg"))) * (M_PI / 180.0); // radians } catch (...) { Debug::log(WARN, "Error parsing gradient {}", V); parseError = "Error parsing gradient " + V; } break; } else // hex rolling = trim(rolling.substr(LAST ? rolling.length() : SPACEPOS + 1)); if (DATA->m_vColors.size() >= 10) { Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V); parseError = "Error parsing gradient " + V + ": max colors is 10."; break; } if (var.empty()) continue; try { DATA->m_vColors.emplace_back(configStringToInt(var)); } catch (std::exception& e) { Debug::log(WARN, "Error parsing gradient {}", V); parseError = "Error parsing gradient " + V + ": " + e.what(); } } if (V.empty()) { DATA->m_bIsFallback = true; DATA->m_vColors.emplace_back(0); // transparent } if (DATA->m_vColors.size() == 0) { Debug::log(WARN, "Error parsing gradient {}", V); parseError = "Error parsing gradient " + V + ": No colors?"; DATA->m_vColors.emplace_back(0); // transparent } DATA->updateColorsOk(); Hyprlang::CParseResult result; if (!parseError.empty()) result.setError(parseError.c_str()); return result; } static void configHandleGradientDestroy(void** data) { if (*data) delete reinterpret_cast(*data); } static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprlock"); if (paths.first.has_value()) return paths.first.value(); else throw std::runtime_error("Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr."); } CConfigManager::CConfigManager(std::string configPath) : m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = configPath.empty()}) { configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath; } inline static constexpr auto GRADIENTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE { return Hyprlang::CUSTOMTYPE{&configHandleGradientSet, configHandleGradientDestroy, default_value}; }; inline static constexpr auto LAYOUTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE { return Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, default_value}; }; void CConfigManager::init() { #define SHADOWABLE(name) \ m_config.addSpecialConfigValue(name, "shadow_size", Hyprlang::INT{3}); \ m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \ m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \ m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2}); #define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""}); m_config.addConfigValue("general:text_trim", Hyprlang::INT{1}); m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0}); m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0}); m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2}); m_config.addConfigValue("general:screencopy_mode", Hyprlang::INT{0}); m_config.addConfigValue("general:fail_timeout", Hyprlang::INT{2000}); m_config.addConfigValue("auth:pam:enabled", Hyprlang::INT{1}); m_config.addConfigValue("auth:pam:module", Hyprlang::STRING{"hyprlock"}); m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0}); m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"}); m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"}); m_config.addConfigValue("auth:fingerprint:retry_delay", Hyprlang::INT{250}); m_config.addConfigValue("animations:enabled", Hyprlang::INT{1}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "color", Hyprlang::INT{0xFF111111}); m_config.addSpecialConfigValue("background", "blur_size", Hyprlang::INT{8}); m_config.addSpecialConfigValue("background", "blur_passes", Hyprlang::INT{0}); m_config.addSpecialConfigValue("background", "noise", Hyprlang::FLOAT{0.0117}); m_config.addSpecialConfigValue("background", "contrast", Hyprlang::FLOAT{0.8917}); m_config.addSpecialConfigValue("background", "brightness", Hyprlang::FLOAT{0.8172}); m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686}); m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05}); m_config.addSpecialConfigValue("background", "zindex", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("background", "reload_time", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("background", "reload_cmd", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "crossfade_time", Hyprlang::FLOAT{-1.0}); m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("shape", "size", LAYOUTCONFIG("100,100")); m_config.addSpecialConfigValue("shape", "rounding", Hyprlang::INT{0}); m_config.addSpecialConfigValue("shape", "border_size", Hyprlang::INT{0}); m_config.addSpecialConfigValue("shape", "border_color", GRADIENTCONFIG("0xFF00CFE6")); m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111}); m_config.addSpecialConfigValue("shape", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("shape", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("shape", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("shape", "rotate", Hyprlang::FLOAT{0}); m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0}); m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0}); SHADOWABLE("shape"); CLICKABLE("shape"); m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("image", "path", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("image", "size", Hyprlang::INT{150}); m_config.addSpecialConfigValue("image", "rounding", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("image", "border_size", Hyprlang::INT{4}); m_config.addSpecialConfigValue("image", "border_color", GRADIENTCONFIG("0xFFDDDDDD")); m_config.addSpecialConfigValue("image", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0}); m_config.addSpecialConfigValue("image", "reload_time", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0}); SHADOWABLE("image"); CLICKABLE("image"); m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("input-field", "size", LAYOUTCONFIG("400,90")); m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD}); m_config.addSpecialConfigValue("input-field", "outer_color", GRADIENTCONFIG("0xFF111111")); m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4}); m_config.addSpecialConfigValue("input-field", "dots_size", Hyprlang::FLOAT{0.25}); m_config.addSpecialConfigValue("input-field", "dots_center", Hyprlang::INT{1}); m_config.addSpecialConfigValue("input-field", "dots_spacing", Hyprlang::FLOAT{0.2}); m_config.addSpecialConfigValue("input-field", "dots_rounding", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("input-field", "dots_text_format", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("input-field", "fade_on_empty", Hyprlang::INT{1}); m_config.addSpecialConfigValue("input-field", "fade_timeout", Hyprlang::INT{2000}); m_config.addSpecialConfigValue("input-field", "font_color", Hyprlang::INT{0xFF000000}); m_config.addSpecialConfigValue("input-field", "font_family", Hyprlang::STRING{"Sans"}); m_config.addSpecialConfigValue("input-field", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("input-field", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"Input Password"}); m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "hide_input_base_color", Hyprlang::INT{0xEE00FF99}); m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88")); m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222")); m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"$FAIL"}); m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG("")); m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG("")); m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG("")); m_config.addSpecialConfigValue("input-field", "invert_numlock", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "swap_font_color", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "zindex", Hyprlang::INT{0}); SHADOWABLE("input-field"); m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("label", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("label", "color", Hyprlang::INT{0xFFFFFFFF}); m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16}); m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"}); m_config.addSpecialConfigValue("label", "font_family", Hyprlang::STRING{"Sans"}); m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("label", "rotate", Hyprlang::FLOAT{0}); m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0}); SHADOWABLE("label"); CLICKABLE("label"); m_config.registerHandler(&::handleSource, "source", {.allowFlags = false}); m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false}); m_config.registerHandler(&::handleAnimation, "animation", {.allowFlags = false}); // // Init Animations // m_AnimationTree.createNode("global"); // toplevel m_AnimationTree.createNode("fade", "global"); m_AnimationTree.createNode("inputField", "global"); // inputField m_AnimationTree.createNode("inputFieldColors", "inputField"); m_AnimationTree.createNode("inputFieldFade", "inputField"); m_AnimationTree.createNode("inputFieldWidth", "inputField"); m_AnimationTree.createNode("inputFieldDots", "inputField"); // fade m_AnimationTree.createNode("fadeIn", "fade"); m_AnimationTree.createNode("fadeOut", "fade"); // set config for root node m_AnimationTree.setConfigForNode("global", 1, 8.f, "default"); m_AnimationTree.setConfigForNode("inputFieldColors", 1, 8.f, "linear"); m_config.commence(); auto result = m_config.parse(); if (result.error) Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError()); #undef SHADOWABLE #undef CLICKABLE } std::vector CConfigManager::getWidgetConfigs() { std::vector result; #define SHADOWABLE(name) \ {"shadow_size", m_config.getSpecialConfigValue(name, "shadow_size", k.c_str())}, {"shadow_passes", m_config.getSpecialConfigValue(name, "shadow_passes", k.c_str())}, \ {"shadow_color", m_config.getSpecialConfigValue(name, "shadow_color", k.c_str())}, { \ "shadow_boost", m_config.getSpecialConfigValue(name, "shadow_boost", k.c_str()) \ } #define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())} // auto keys = m_config.listKeysForSpecialCategory("background"); result.reserve(keys.size()); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ .type = "background", .monitor = std::any_cast(m_config.getSpecialConfigValue("background", "monitor", k.c_str())), .values = { {"path", m_config.getSpecialConfigValue("background", "path", k.c_str())}, {"color", m_config.getSpecialConfigValue("background", "color", k.c_str())}, {"blur_size", m_config.getSpecialConfigValue("background", "blur_size", k.c_str())}, {"blur_passes", m_config.getSpecialConfigValue("background", "blur_passes", k.c_str())}, {"noise", m_config.getSpecialConfigValue("background", "noise", k.c_str())}, {"contrast", m_config.getSpecialConfigValue("background", "contrast", k.c_str())}, {"vibrancy", m_config.getSpecialConfigValue("background", "vibrancy", k.c_str())}, {"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())}, {"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("background", "zindex", k.c_str())}, {"reload_time", m_config.getSpecialConfigValue("background", "reload_time", k.c_str())}, {"reload_cmd", m_config.getSpecialConfigValue("background", "reload_cmd", k.c_str())}, {"crossfade_time", m_config.getSpecialConfigValue("background", "crossfade_time", k.c_str())}, } }); // clang-format on } // keys = m_config.listKeysForSpecialCategory("shape"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ .type = "shape", .monitor = std::any_cast(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())), .values = { {"size", m_config.getSpecialConfigValue("shape", "size", k.c_str())}, {"rounding", m_config.getSpecialConfigValue("shape", "rounding", k.c_str())}, {"border_size", m_config.getSpecialConfigValue("shape", "border_size", k.c_str())}, {"border_color", m_config.getSpecialConfigValue("shape", "border_color", k.c_str())}, {"color", m_config.getSpecialConfigValue("shape", "color", k.c_str())}, {"position", m_config.getSpecialConfigValue("shape", "position", k.c_str())}, {"halign", m_config.getSpecialConfigValue("shape", "halign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("shape", "valign", k.c_str())}, {"rotate", m_config.getSpecialConfigValue("shape", "rotate", k.c_str())}, {"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())}, SHADOWABLE("shape"), CLICKABLE("shape"), } }); // clang-format on } // keys = m_config.listKeysForSpecialCategory("image"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ .type = "image", .monitor = std::any_cast(m_config.getSpecialConfigValue("image", "monitor", k.c_str())), .values = { {"path", m_config.getSpecialConfigValue("image", "path", k.c_str())}, {"size", m_config.getSpecialConfigValue("image", "size", k.c_str())}, {"rounding", m_config.getSpecialConfigValue("image", "rounding", k.c_str())}, {"border_size", m_config.getSpecialConfigValue("image", "border_size", k.c_str())}, {"border_color", m_config.getSpecialConfigValue("image", "border_color", k.c_str())}, {"position", m_config.getSpecialConfigValue("image", "position", k.c_str())}, {"halign", m_config.getSpecialConfigValue("image", "halign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("image", "valign", k.c_str())}, {"rotate", m_config.getSpecialConfigValue("image", "rotate", k.c_str())}, {"reload_time", m_config.getSpecialConfigValue("image", "reload_time", k.c_str())}, {"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())}, SHADOWABLE("image"), CLICKABLE("image"), } }); // clang-format on } keys = m_config.listKeysForSpecialCategory("input-field"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ .type = "input-field", .monitor = std::any_cast(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())), .values = { {"size", m_config.getSpecialConfigValue("input-field", "size", k.c_str())}, {"inner_color", m_config.getSpecialConfigValue("input-field", "inner_color", k.c_str())}, {"outer_color", m_config.getSpecialConfigValue("input-field", "outer_color", k.c_str())}, {"outline_thickness", m_config.getSpecialConfigValue("input-field", "outline_thickness", k.c_str())}, {"dots_size", m_config.getSpecialConfigValue("input-field", "dots_size", k.c_str())}, {"dots_spacing", m_config.getSpecialConfigValue("input-field", "dots_spacing", k.c_str())}, {"dots_center", m_config.getSpecialConfigValue("input-field", "dots_center", k.c_str())}, {"dots_rounding", m_config.getSpecialConfigValue("input-field", "dots_rounding", k.c_str())}, {"dots_text_format", m_config.getSpecialConfigValue("input-field", "dots_text_format", k.c_str())}, {"fade_on_empty", m_config.getSpecialConfigValue("input-field", "fade_on_empty", k.c_str())}, {"fade_timeout", m_config.getSpecialConfigValue("input-field", "fade_timeout", k.c_str())}, {"font_color", m_config.getSpecialConfigValue("input-field", "font_color", k.c_str())}, {"font_family", m_config.getSpecialConfigValue("input-field", "font_family", k.c_str())}, {"halign", m_config.getSpecialConfigValue("input-field", "halign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("input-field", "valign", k.c_str())}, {"position", m_config.getSpecialConfigValue("input-field", "position", k.c_str())}, {"placeholder_text", m_config.getSpecialConfigValue("input-field", "placeholder_text", k.c_str())}, {"hide_input", m_config.getSpecialConfigValue("input-field", "hide_input", k.c_str())}, {"hide_input_base_color", m_config.getSpecialConfigValue("input-field", "hide_input_base_color", k.c_str())}, {"rounding", m_config.getSpecialConfigValue("input-field", "rounding", k.c_str())}, {"check_color", m_config.getSpecialConfigValue("input-field", "check_color", k.c_str())}, {"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())}, {"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())}, {"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())}, {"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())}, {"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())}, {"invert_numlock", m_config.getSpecialConfigValue("input-field", "invert_numlock", k.c_str())}, {"swap_font_color", m_config.getSpecialConfigValue("input-field", "swap_font_color", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("input-field", "zindex", k.c_str())}, SHADOWABLE("input-field"), } }); // clang-format on } keys = m_config.listKeysForSpecialCategory("label"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ .type = "label", .monitor = std::any_cast(m_config.getSpecialConfigValue("label", "monitor", k.c_str())), .values = { {"position", m_config.getSpecialConfigValue("label", "position", k.c_str())}, {"color", m_config.getSpecialConfigValue("label", "color", k.c_str())}, {"font_size", m_config.getSpecialConfigValue("label", "font_size", k.c_str())}, {"font_family", m_config.getSpecialConfigValue("label", "font_family", k.c_str())}, {"text", m_config.getSpecialConfigValue("label", "text", k.c_str())}, {"halign", m_config.getSpecialConfigValue("label", "halign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("label", "valign", k.c_str())}, {"rotate", m_config.getSpecialConfigValue("label", "rotate", k.c_str())}, {"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())}, SHADOWABLE("label"), CLICKABLE("label"), } }); // clang-format on } return result; } std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { if (rawpath.length() < 2) { Debug::log(ERR, "source= path garbage"); return "source path " + rawpath + " bogus!"; } std::unique_ptr glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; memset(glob_buf.get(), 0, sizeof(glob_t)); const auto CURRENTDIR = std::filesystem::path(configCurrentPath).parent_path().string(); if (auto r = glob(absolutePath(rawpath, CURRENTDIR).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); Debug::log(ERR, "{}", err); return err; } for (size_t i = 0; i < glob_buf->gl_pathc; i++) { const auto PATH = absolutePath(glob_buf->gl_pathv[i], CURRENTDIR); if (PATH.empty() || PATH == configCurrentPath) { Debug::log(WARN, "source= skipping invalid path"); continue; } if (!std::filesystem::is_regular_file(PATH)) { if (std::filesystem::exists(PATH)) { Debug::log(WARN, "source= skipping non-file {}", PATH); continue; } Debug::log(ERR, "source= file doesnt exist"); return "source file " + PATH + " doesn't exist!"; } // allow for nested config parsing auto backupConfigPath = configCurrentPath; configCurrentPath = PATH; m_config.parseFile(PATH.c_str()); configCurrentPath = backupConfigPath; } return {}; } std::optional CConfigManager::handleBezier(const std::string& command, const std::string& args) { const auto ARGS = CVarList(args); std::string bezierName = ARGS[0]; if (ARGS[1] == "") return "too few arguments"; float p1x = std::stof(ARGS[1]); if (ARGS[2] == "") return "too few arguments"; float p1y = std::stof(ARGS[2]); if (ARGS[3] == "") return "too few arguments"; float p2x = std::stof(ARGS[3]); if (ARGS[4] == "") return "too few arguments"; float p2y = std::stof(ARGS[4]); if (ARGS[5] != "") return "too many arguments"; g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y)); return {}; } std::optional CConfigManager::handleAnimation(const std::string& command, const std::string& args) { const auto ARGS = CVarList(args); const auto ANIMNAME = ARGS[0]; if (!m_AnimationTree.nodeExists(ANIMNAME)) return "no such animation"; // This helper casts strings like "1", "true", "off", "yes"... to int. int64_t enabledInt = configStringToInt(ARGS[1]); // Checking that the int is 1 or 0 because the helper can return integers out of range. if (enabledInt > 1 || enabledInt < 0) return "invalid animation on/off state"; if (!enabledInt) { m_AnimationTree.setConfigForNode(ANIMNAME, 0, 1, "default"); return {}; } int64_t speed = -1; // speed if (isNumber(ARGS[2], true)) { speed = std::stof(ARGS[2]); if (speed <= 0) { speed = 1.f; return "invalid speed"; } } else { speed = 10.f; return "invalid speed"; } std::string bezierName = ARGS[3]; // ARGS[4] (style) currently usused by hyprlock m_AnimationTree.setConfigForNode(ANIMNAME, enabledInt, speed, bezierName, ""); if (!g_pAnimationManager->bezierExists(bezierName)) { const auto PANIMNODE = m_AnimationTree.getConfig(ANIMNAME); PANIMNODE->internalBezier = "default"; return "no such bezier"; } return {}; } hyprlock-0.9.2/src/config/ConfigManager.hpp000066400000000000000000000023551506744673100206440ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "../defines.hpp" class CConfigManager { public: CConfigManager(std::string configPath); void init(); template Hyprlang::CSimpleConfigValue getValue(const std::string& name) { return Hyprlang::CSimpleConfigValue(&m_config, name.c_str()); } struct SWidgetConfig { std::string type; std::string monitor; std::unordered_map values; }; std::vector getWidgetConfigs(); std::optional handleSource(const std::string&, const std::string&); std::optional handleBezier(const std::string&, const std::string&); std::optional handleAnimation(const std::string&, const std::string&); std::string configCurrentPath; Hyprutils::Animation::CAnimationConfigTree m_AnimationTree; private: Hyprlang::CConfig m_config; }; inline UP g_pConfigManager; hyprlock-0.9.2/src/core/000077500000000000000000000000001506744673100151115ustar00rootroot00000000000000hyprlock-0.9.2/src/core/AnimationManager.cpp000066400000000000000000000115501506744673100210310ustar00rootroot00000000000000#include "AnimationManager.hpp" #include "../helpers/AnimatedVariable.hpp" #include "../config/ConfigDataValues.hpp" #include "../config/ConfigManager.hpp" CHyprlockAnimationManager::CHyprlockAnimationManager() { addBezierWithName("linear", {0, 0}, {1, 1}); } template void updateVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || !av.enabled() || av.value() == av.goal()) { av.warp(true, false); return; } const auto DELTA = av.goal() - av.begun(); av.value() = av.begun() + DELTA * POINTY; } void updateColorVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || !av.enabled() || av.value() == av.goal()) { av.warp(true, false); return; } // convert both to OkLab, then lerp that, and convert back. // This is not as fast as just lerping rgb, but it's WAY more precise... // Use the CHyprColor cache for OkLab const auto& L1 = av.begun().asOkLab(); const auto& L2 = av.goal().asOkLab(); static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); }; const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{ .l = lerp(L1.l, L2.l, POINTY), .a = lerp(L1.a, L2.a, POINTY), .b = lerp(L1.b, L2.b, POINTY), }; av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)}; } void updateGradientVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || av.value() == av.goal()) { av.warp(true, false); return; } av.value().m_vColors.resize(av.goal().m_vColors.size(), av.goal().m_vColors.back()); for (size_t i = 0; i < av.value().m_vColors.size(); ++i) { const CHyprColor& sourceCol = (i < av.begun().m_vColors.size()) ? av.begun().m_vColors[i] : av.begun().m_vColors.back(); const CHyprColor& targetCol = (i < av.goal().m_vColors.size()) ? av.goal().m_vColors[i] : av.goal().m_vColors.back(); const auto& L1 = sourceCol.asOkLab(); const auto& L2 = targetCol.asOkLab(); static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); }; const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{ .l = lerp(L1.l, L2.l, POINTY), .a = lerp(L1.a, L2.a, POINTY), .b = lerp(L1.b, L2.b, POINTY), }; av.value().m_vColors[i] = {lerped, lerp(sourceCol.a, targetCol.a, POINTY)}; } if (av.begun().m_fAngle != av.goal().m_fAngle) { const float DELTA = av.goal().m_fAngle - av.begun().m_fAngle; av.value().m_fAngle = av.begun().m_fAngle + DELTA * POINTY; } av.value().updateColorsOk(); } void CHyprlockAnimationManager::tick() { static const auto ANIMATIONSENABLED = g_pConfigManager->getValue("animations:enabled"); for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { const auto PAV = m_vActiveAnimatedVariables[i].lock(); if (!PAV || !PAV->ok()) continue; const auto SPENT = PAV->getPercent(); const auto PBEZIER = getBezier(PAV->getBezierName()); const auto POINTY = PBEZIER->getYForPoint(SPENT); const bool WARP = !*ANIMATIONSENABLED || SPENT >= 1.f; switch (PAV->m_Type) { case AVARTYPE_FLOAT: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated float"); updateVariable(*pTypedAV, POINTY, WARP); } break; case AVARTYPE_VECTOR: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); updateVariable(*pTypedAV, POINTY, WARP); } break; case AVARTYPE_COLOR: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); updateColorVariable(*pTypedAV, POINTY, WARP); } break; case AVARTYPE_GRADIENT: { auto pTypedAV = dynamic_cast*>(PAV.get()); RASSERT(pTypedAV, "Failed to upcast animated CGradientValueData"); updateGradientVariable(*pTypedAV, POINTY, WARP); } break; default: continue; } PAV->onUpdate(); } tickDone(); } void CHyprlockAnimationManager::scheduleTick() { m_bTickScheduled = true; } void CHyprlockAnimationManager::onTicked() { m_bTickScheduled = false; } hyprlock-0.9.2/src/core/AnimationManager.hpp000066400000000000000000000020641506744673100210360ustar00rootroot00000000000000#pragma once #include #include #include "../helpers/AnimatedVariable.hpp" #include "../defines.hpp" class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager { public: CHyprlockAnimationManager(); void tick(); virtual void scheduleTick(); virtual void onTicked(); using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig; template void createAnimation(const VarType& v, PHLANIMVAR& pav, SP pConfig) { constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType; const auto PAV = makeShared>(); PAV->create(EAVTYPE, static_cast(this), PAV, v); PAV->setConfig(pConfig); pav = std::move(PAV); } bool m_bTickScheduled = false; }; inline UP g_pAnimationManager; hyprlock-0.9.2/src/core/CursorShape.cpp000066400000000000000000000013321506744673100200520ustar00rootroot00000000000000#include "CursorShape.hpp" #include "Seat.hpp" CCursorShape::CCursorShape(SP mgr) : mgr(mgr) { if (!g_pSeatManager->m_pPointer) return; dev = makeShared(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource())); } void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) { if (!g_pSeatManager->m_pPointer) return; if (!dev) dev = makeShared(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource())); shapeChanged = true; dev->sendSetShape(lastCursorSerial, shape); } void CCursorShape::hideCursor() { g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0); } hyprlock-0.9.2/src/core/CursorShape.hpp000066400000000000000000000006601506744673100200620ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "cursor-shape-v1.hpp" class CCursorShape { public: CCursorShape(SP mgr); void setShape(const wpCursorShapeDeviceV1Shape shape); void hideCursor(); uint32_t lastCursorSerial = 0; bool shapeChanged = false; private: SP mgr = nullptr; SP dev = nullptr; }; hyprlock-0.9.2/src/core/Egl.cpp000066400000000000000000000057431506744673100163350ustar00rootroot00000000000000#include "Egl.hpp" #include "../helpers/Log.hpp" PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE, }; const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, }; CEGL::CEGL(wl_display* display) { const char* _EXTS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!_EXTS) { if (eglGetError() == EGL_BAD_DISPLAY) throw std::runtime_error("EGL_EXT_client_extensions not supported"); else throw std::runtime_error("Failed to query EGL client extensions"); } std::string EXTS = _EXTS; if (!EXTS.contains("EGL_EXT_platform_base")) throw std::runtime_error("EGL_EXT_platform_base not supported"); if (!EXTS.contains("EGL_EXT_platform_wayland")) throw std::runtime_error("EGL_EXT_platform_wayland not supported"); eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); if (eglGetPlatformDisplayEXT == nullptr) throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT"); eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); if (eglCreatePlatformWindowSurfaceEXT == nullptr) throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT"); const char* vendorString = nullptr; eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr); EGLint matched = 0; if (eglDisplay == EGL_NO_DISPLAY) { Debug::log(CRIT, "Failed to create EGL display"); goto error; } if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) { Debug::log(CRIT, "Failed to initialize EGL"); goto error; } if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) { Debug::log(CRIT, "eglChooseConfig failed"); goto error; } if (matched == 0) { Debug::log(CRIT, "Failed to match an EGL config"); goto error; } eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs); if (eglContext == EGL_NO_CONTEXT) { Debug::log(CRIT, "Failed to create EGL context"); goto error; } vendorString = eglQueryString(eglDisplay, EGL_VENDOR); m_isNvidia = (vendorString) ? std::string{vendorString}.contains("NVIDIA") : false; return; error: eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } CEGL::~CEGL() { if (eglContext != EGL_NO_CONTEXT) eglDestroyContext(eglDisplay, eglContext); if (eglDisplay) eglTerminate(eglDisplay); eglReleaseThread(); } void CEGL::makeCurrent(EGLSurface surf) { eglMakeCurrent(eglDisplay, surf, surf, eglContext); } hyprlock-0.9.2/src/core/Egl.hpp000066400000000000000000000010701506744673100163270ustar00rootroot00000000000000#pragma once #include #include #include "../defines.hpp" class CEGL { public: CEGL(wl_display*); ~CEGL(); EGLDisplay eglDisplay; EGLConfig eglConfig; EGLContext eglContext; PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT; void makeCurrent(EGLSurface surf); bool m_isNvidia = false; }; inline UP g_pEGL; hyprlock-0.9.2/src/core/LockSurface.cpp000066400000000000000000000122371506744673100200230ustar00rootroot00000000000000#include "LockSurface.hpp" #include "hyprlock.hpp" #include "Egl.hpp" #include "../config/ConfigManager.hpp" #include "../core/AnimationManager.hpp" #include "../helpers/Log.hpp" #include "../renderer/Renderer.hpp" CSessionLockSurface::~CSessionLockSurface() { if (frameCallback) frameCallback.reset(); if (eglSurface) eglDestroySurface(g_pEGL->eglDisplay, eglSurface); if (eglWindow) wl_egl_window_destroy(eglWindow); } CSessionLockSurface::CSessionLockSurface(const SP& pOutput) : m_outputRef(pOutput), m_outputID(pOutput->m_ID) { surface = makeShared(g_pHyprlock->getCompositor()->sendCreateSurface()); RASSERT(surface, "Couldn't create wl_surface"); static const auto FRACTIONALSCALING = g_pConfigManager->getValue("general:fractional_scaling"); const auto ENABLE_FSV1 = *FRACTIONALSCALING == 1 || /* auto enable */ (*FRACTIONALSCALING == 2); const auto PFRACTIONALMGR = g_pHyprlock->getFractionalMgr(); const auto PVIEWPORTER = g_pHyprlock->getViewporter(); if (ENABLE_FSV1 && PFRACTIONALMGR && PVIEWPORTER) { fractional = makeShared(PFRACTIONALMGR->sendGetFractionalScale(surface->resource())); fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { const bool SAMESCALE = fractionalScale == scale / 120.0; fractionalScale = scale / 120.0; Debug::log(LOG, "Got fractional scale: {:.1f}%", fractionalScale * 100.F); if (!SAMESCALE && readyForFrame) onScaleUpdate(); }); viewport = makeShared(PVIEWPORTER->sendGetViewport(surface->resource())); } if (!PFRACTIONALMGR) Debug::log(LOG, "No fractional-scale support! Oops, won't be able to scale!"); if (!PVIEWPORTER) Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!"); lockSurface = makeShared(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource())); RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1"); lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) { configure({(double)width, (double)height}, serial); }); } void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) { Debug::log(LOG, "configure with serial {}", serial_); const bool SAMESERIAL = serial == serial_; const bool SAMESIZE = logicalSize == size_; const bool SAMESCALE = appliedScale == fractionalScale; const auto POUTPUT = m_outputRef.lock(); serial = serial_; logicalSize = size_; appliedScale = fractionalScale; if (fractional) { size = (size_ * fractionalScale).floor(); viewport->sendSetDestination(logicalSize.x, logicalSize.y); surface->sendSetBufferScale(1); } else { size = size_ * POUTPUT->scale; surface->sendSetBufferScale(POUTPUT->scale); } if (!SAMESERIAL) lockSurface->sendAckConfigure(serial); Debug::log(LOG, "Configuring surface for logical {} and pixel {}", logicalSize, size); surface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF); if (!eglWindow) { eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y); RASSERT(eglWindow, "Couldn't create eglWindow"); } else wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0); if (!eglSurface) { eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr); RASSERT(eglSurface, "Couldn't create eglSurface"); } if (readyForFrame && !(SAMESIZE && SAMESCALE)) { Debug::log(LOG, "output {} changed, reloading widgets!", POUTPUT->stringPort); g_pRenderer->reconfigureWidgetsFor(POUTPUT->m_ID); } readyForFrame = true; render(); } void CSessionLockSurface::onScaleUpdate() { configure(logicalSize, serial); } void CSessionLockSurface::render() { if (frameCallback || !readyForFrame) { needsFrame = true; return; } g_pAnimationManager->tick(); const auto FEEDBACK = g_pRenderer->renderLock(*this); frameCallback = makeShared(surface->sendFrame()); frameCallback->setDone([this](CCWlCallback* r, uint32_t frameTime) { if (g_pHyprlock->m_bTerminate) return; if (Debug::verbose) { const auto POUTPUT = m_outputRef.lock(); Debug::log(TRACE, "[{}] frame {}, Current fps: {:.2f}", POUTPUT->stringPort, m_frames, 1000.f / (frameTime - m_lastFrameTime)); } m_lastFrameTime = frameTime; m_frames++; onCallback(); }); eglSwapBuffers(g_pEGL->eglDisplay, eglSurface); needsFrame = FEEDBACK.needsFrame || g_pAnimationManager->shouldTickForNext(); } void CSessionLockSurface::onCallback() { frameCallback.reset(); if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL) { needsFrame = false; render(); } } SP CSessionLockSurface::getWlSurface() { return surface; } hyprlock-0.9.2/src/core/LockSurface.hpp000066400000000000000000000031361506744673100200260ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "wayland.hpp" #include "ext-session-lock-v1.hpp" #include "viewporter.hpp" #include "fractional-scale-v1.hpp" #include "../helpers/Math.hpp" #include #include class COutput; class CRenderer; class CSessionLockSurface { public: CSessionLockSurface(const SP& pOutput); ~CSessionLockSurface(); void configure(const Vector2D& size, uint32_t serial); bool readyForFrame = false; float fractionalScale = 1.0; void render(); void onCallback(); void onScaleUpdate(); SP getWlSurface(); private: WP m_outputRef; OUTPUTID m_outputID = OUTPUT_INVALID; SP surface = nullptr; SP lockSurface = nullptr; uint32_t serial = 0; wl_egl_window* eglWindow = nullptr; Vector2D size; Vector2D logicalSize; float appliedScale; EGLSurface eglSurface = nullptr; SP fractional = nullptr; SP viewport = nullptr; bool needsFrame = false; uint32_t m_lastFrameTime = 0; uint32_t m_frames = 0; // wayland callbacks SP frameCallback = nullptr; friend class CRenderer; friend class COutput; }; hyprlock-0.9.2/src/core/Output.cpp000066400000000000000000000045121506744673100171170ustar00rootroot00000000000000#include "Output.hpp" #include "../helpers/Log.hpp" #include "hyprlock.hpp" void COutput::create(WP pSelf, SP pWlOutput, uint32_t _name) { m_ID = _name; m_wlOutput = pWlOutput; m_self = pSelf; m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) { stringDesc = description ? std::string{description} : ""; Debug::log(LOG, "output {} description {}", m_ID, stringDesc); }); m_wlOutput->setName([this](CCWlOutput* r, const char* name) { stringName = std::string{name} + stringName; stringPort = std::string{name}; Debug::log(LOG, "output {} name {}", name, name); }); m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; }); m_wlOutput->setDone([this](CCWlOutput* r) { done = true; Debug::log(LOG, "output {} done", m_ID); if (g_pHyprlock->m_lockAquired && !m_sessionLockSurface) { Debug::log(LOG, "output {} creating a new lock surface", m_ID); createSessionLockSurface(); } }); m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { // handle portrait mode and flipped cases if (transform % 2 == 1) size = {height, width}; else size = {width, height}; }); m_wlOutput->setGeometry( [this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) { transform = (wl_output_transform)transform_; Debug::log(LOG, "output {} make {} model {}", m_ID, make ? make : "", model ? model : ""); }); } void COutput::createSessionLockSurface() { if (!m_self.valid()) { Debug::log(ERR, "output {} dead??", m_ID); return; } if (m_sessionLockSurface) { Debug::log(ERR, "output {} already has a session lock surface", m_ID); return; } if (size == Vector2D{0, 0}) { Debug::log(WARN, "output {} refusing to create a lock surface with size 0x0", m_ID); return; } m_sessionLockSurface = makeUnique(m_self.lock()); } Vector2D COutput::getViewport() const { return (m_sessionLockSurface) ? m_sessionLockSurface->size : size; } hyprlock-0.9.2/src/core/Output.hpp000066400000000000000000000016461506744673100171310ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "wayland.hpp" #include "LockSurface.hpp" class COutput { public: COutput() = default; ~COutput() = default; void create(WP pSelf, SP pWlOutput, uint32_t name); OUTPUTID m_ID = 0; bool focused = false; bool done = false; wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; Vector2D size; int scale = 1; std::string stringName = ""; std::string stringPort = ""; std::string stringDesc = ""; UP m_sessionLockSurface; SP m_wlOutput = nullptr; WP m_self; void createSessionLockSurface(); Vector2D getViewport() const; }; hyprlock-0.9.2/src/core/Seat.cpp000066400000000000000000000164761506744673100165270ustar00rootroot00000000000000#include "Seat.hpp" #include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include #include #include #include CSeatManager::~CSeatManager() { if (m_pCursorShape && m_pCursorShape->shapeChanged) m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); if (m_pXKBState) xkb_state_unref(m_pXKBState); if (m_pXKBKeymap) xkb_keymap_unref(m_pXKBKeymap); if (m_pXKBContext) xkb_context_unref(m_pXKBContext); } void CSeatManager::registerSeat(SP seat) { m_pSeat = seat; m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!m_pXKBContext) Debug::log(ERR, "Failed to create xkb context"); m_pSeat->setCapabilities([this](CCWlSeat* r, wl_seat_capability caps) { if (caps & WL_SEAT_CAPABILITY_POINTER) { m_pPointer = makeShared(r->sendGetPointer()); static const auto HIDECURSOR = g_pConfigManager->getValue("general:hide_cursor"); m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}; if (!*HIDECURSOR) g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation); if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds) return; if (!g_pHyprlock->isUnlocked() && g_pHyprlock->m_vLastEnterCoords.distance({wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}) > 5) { Debug::log(LOG, "In grace and cursor moved more than 5px, unlocking!"); g_pHyprlock->unlock(); } }); m_pPointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surf, wl_fixed_t surface_x, wl_fixed_t surface_y) { if (!m_pCursorShape) return; m_pCursorShape->lastCursorSerial = serial; if (*HIDECURSOR) m_pCursorShape->hideCursor(); else m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}; if (*HIDECURSOR) return; for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) { if (!POUTPUT->m_sessionLockSurface) continue; const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface(); if (PWLSURFACE->resource() == surf) g_pHyprlock->m_focusedOutput = POUTPUT; } }); m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); }); m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) { if (*HIDECURSOR) return; g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation); }); } if (caps & WL_SEAT_CAPABILITY_TOUCH) { m_pTouch = makeShared(r->sendGetTouch()); m_pTouch->setDown([](CCWlTouch* r, uint32_t serial, uint32_t time, wl_proxy* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { g_pHyprlock->onClick(BTN_LEFT, true, {wl_fixed_to_double(x), wl_fixed_to_double(y)}); }); m_pTouch->setUp([](CCWlTouch* r, uint32_t serial, uint32_t time, int32_t id) { g_pHyprlock->onClick(BTN_LEFT, false, {0, 0}); }); }; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { m_pKeeb = makeShared(r->sendGetKeyboard()); m_pKeeb->setKeymap([this](CCWlKeyboard*, wl_keyboard_keymap_format format, int32_t fd, uint32_t size) { if (!m_pXKBContext) return; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { Debug::log(ERR, "Could not recognise keymap format"); return; } const char* buf = (const char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { Debug::log(ERR, "Failed to mmap xkb keymap: {}", errno); return; } m_pXKBKeymap = xkb_keymap_new_from_buffer(m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap((void*)buf, size); close(fd); if (!m_pXKBKeymap) { Debug::log(ERR, "Failed to compile xkb keymap"); return; } m_pXKBState = xkb_state_new(m_pXKBKeymap); if (!m_pXKBState) { Debug::log(ERR, "Failed to create xkb state"); return; } const auto PCOMOPOSETABLE = xkb_compose_table_new_from_locale(m_pXKBContext, setlocale(LC_CTYPE, nullptr), XKB_COMPOSE_COMPILE_NO_FLAGS); if (!PCOMOPOSETABLE) { Debug::log(ERR, "Failed to create xkb compose table"); return; } m_pXKBComposeState = xkb_compose_state_new(PCOMOPOSETABLE, XKB_COMPOSE_STATE_NO_FLAGS); }); m_pKeeb->setKey([](CCWlKeyboard* r, uint32_t serial, uint32_t time, uint32_t key, wl_keyboard_key_state state) { g_pHyprlock->onKey(key, state == WL_KEYBOARD_KEY_STATE_PRESSED); }); m_pKeeb->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { if (!m_pXKBState) return; if (group != g_pHyprlock->m_uiActiveLayout) { g_pHyprlock->m_uiActiveLayout = group; for (auto& t : g_pHyprlock->getTimers()) { if (t->canForceUpdate()) { t->call(t); t->cancel(); } } } xkb_state_update_mask(m_pXKBState, mods_depressed, mods_latched, mods_locked, 0, 0, group); g_pHyprlock->m_bCapsLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); g_pHyprlock->m_bNumLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED); }); m_pKeeb->setRepeatInfo([](CCWlKeyboard* r, int32_t rate, int32_t delay) { g_pHyprlock->m_iKeebRepeatRate = rate; g_pHyprlock->m_iKeebRepeatDelay = delay; }); } }); m_pSeat->setName([](CCWlSeat* r, const char* name) { Debug::log(LOG, "Exposed seat name: {}", name ? name : "nullptr"); }); } void CSeatManager::registerCursorShape(SP shape) { m_pCursorShape = makeUnique(shape); } bool CSeatManager::registered() { return m_pSeat; } hyprlock-0.9.2/src/core/Seat.hpp000066400000000000000000000015661506744673100165260ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "CursorShape.hpp" #include "wayland.hpp" #include #include class CSeatManager { public: CSeatManager() = default; ~CSeatManager(); void registerSeat(SP seat); void registerCursorShape(SP shape); bool registered(); SP m_pKeeb; SP m_pPointer; SP m_pTouch; UP m_pCursorShape; xkb_context* m_pXKBContext = nullptr; xkb_keymap* m_pXKBKeymap = nullptr; xkb_state* m_pXKBState = nullptr; xkb_compose_state* m_pXKBComposeState = nullptr; private: SP m_pSeat; }; inline UP g_pSeatManager = makeUnique(); hyprlock-0.9.2/src/core/Timer.cpp000066400000000000000000000013401506744673100166730ustar00rootroot00000000000000#include "Timer.hpp" CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function self, void* data)> cb_, void* data_, bool force) : cb(cb_), data(data_), allowForceUpdate(force) { expires = std::chrono::system_clock::now() + timeout; } bool CTimer::passed() { return std::chrono::system_clock::now() > expires; } void CTimer::cancel() { wasCancelled = true; } bool CTimer::cancelled() { return wasCancelled; } void CTimer::call(ASP self) { cb(self, data); } float CTimer::leftMs() { return std::chrono::duration_cast(expires - std::chrono::system_clock::now()).count(); } bool CTimer::canForceUpdate() { return allowForceUpdate; } hyprlock-0.9.2/src/core/Timer.hpp000066400000000000000000000013651506744673100167070ustar00rootroot00000000000000#pragma once #include #include #include "../defines.hpp" class CTimer { public: CTimer(std::chrono::system_clock::duration timeout, std::function self, void* data)> cb_, void* data_, bool force); void cancel(); bool passed(); bool canForceUpdate(); float leftMs(); bool cancelled(); void call(ASP self); private: std::function self, void* data)> cb; void* data = nullptr; std::chrono::system_clock::time_point expires; bool wasCancelled = false; bool allowForceUpdate = false; }; hyprlock-0.9.2/src/core/hyprlock.cpp000066400000000000000000001002361506744673100174520ustar00rootroot00000000000000#include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" #include "../renderer/AsyncResourceGatherer.hpp" #include "../auth/Auth.hpp" #include "../auth/Fingerprint.hpp" #include "Egl.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::OS; static void setMallocThreshold() { #ifdef M_TRIM_THRESHOLD // The default is 128 pages, // which is very large and can lead to a lot of memory used for no reason // because trimming hasn't happened static const int PAGESIZE = sysconf(_SC_PAGESIZE); mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE); #endif } CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediateRender, const int graceSeconds) { setMallocThreshold(); m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str()); RASSERT(m_sWaylandState.display, "Couldn't connect to a wayland compositor"); g_pEGL = makeUnique(m_sWaylandState.display); if (graceSeconds > 0) m_tGraceEnds = std::chrono::system_clock::now() + std::chrono::seconds(graceSeconds); else m_tGraceEnds = std::chrono::system_clock::from_time_t(0); static const auto IMMEDIATERENDER = g_pConfigManager->getValue("general:immediate_render"); m_bImmediateRender = immediateRender || *IMMEDIATERENDER; const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP"); const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""}; m_sCurrentDesktop = SZCURRENTD; } CHyprlock::~CHyprlock() { if (dma.gbmDevice) gbm_device_destroy(dma.gbmDevice); } static void registerSignalAction(int sig, void (*handler)(int), int sa_flags = 0) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = sa_flags; sigaction(sig, &sa, nullptr); } static void handleUnlockSignal(int sig) { if (sig == SIGUSR1) { Debug::log(LOG, "Unlocking with a SIGUSR1"); g_pAuth->enqueueUnlock(); } } static void handleForceUpdateSignal(int sig) { if (sig == SIGUSR2) { for (auto& t : g_pHyprlock->getTimers()) { if (t->canForceUpdate()) { t->call(t); t->cancel(); } } } } static void handlePollTerminate(int sig) { ; } static char* gbm_find_render_node(drmDevice* device) { drmDevice* devices[64]; char* render_node = nullptr; int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); for (int i = 0; i < n; ++i) { drmDevice* dev = devices[i]; if (device && !drmDevicesEqual(device, dev)) { continue; } if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) continue; render_node = strdup(dev->nodes[DRM_NODE_RENDER]); break; } drmFreeDevices(devices, n); return render_node; } gbm_device* CHyprlock::createGBMDevice(drmDevice* dev) { char* renderNode = gbm_find_render_node(dev); if (!renderNode) { Debug::log(ERR, "[core] Couldn't find a render node"); return nullptr; } Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode); int fd = open(renderNode, O_RDWR | O_CLOEXEC); if (fd < 0) { Debug::log(ERR, "[core] couldn't open render node"); free(renderNode); return nullptr; } free(renderNode); return gbm_create_device(fd); } void CHyprlock::addDmabufListener() { dma.linuxDmabufFeedback->setTrancheDone([this](CCZwpLinuxDmabufFeedbackV1* r) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone"); dma.deviceUsed = false; }); dma.linuxDmabufFeedback->setTrancheFormats([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* indices) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats"); if (!dma.deviceUsed || !dma.formatTable) return; struct fm_entry { uint32_t format; uint32_t padding; uint64_t modifier; }; // An entry in the table has to be 16 bytes long static_assert(sizeof(fm_entry) == 16); uint32_t n_modifiers = dma.formatTableSize / sizeof(fm_entry); fm_entry* fm_entry = (struct fm_entry*)dma.formatTable; uint16_t* idx; for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) { if (*idx >= n_modifiers) continue; Debug::log(TRACE, "GPU Reports supported format {:x} with modifier {:x}", (fm_entry + *idx)->format, (fm_entry + *idx)->modifier); dma.dmabufMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier}); } }); dma.linuxDmabufFeedback->setTrancheTargetDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice"); dev_t device; assert(device_arr->size == sizeof(device)); memcpy(&device, device_arr->data, sizeof(device)); drmDevice* drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) return; if (dma.gbmDevice) { drmDevice* drmDevRenderer = nullptr; drmGetDevice2(gbm_device_get_fd(dma.gbmDevice), /* flags */ 0, &drmDevRenderer); dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev); } else { dma.gbmDevice = createGBMDevice(drmDev); dma.deviceUsed = dma.gbm; } }); dma.linuxDmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) { Debug::log(TRACE, "[core] dmabufFeedbackDone"); if (dma.formatTable) munmap(dma.formatTable, dma.formatTableSize); dma.formatTable = nullptr; dma.formatTableSize = 0; }); dma.linuxDmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int fd, uint32_t size) { Debug::log(TRACE, "[core] dmabufFeedbackFormatTable"); dma.dmabufMods.clear(); dma.formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (dma.formatTable == MAP_FAILED) { Debug::log(ERR, "[core] format table failed to mmap"); dma.formatTable = nullptr; dma.formatTableSize = 0; return; } dma.formatTableSize = size; }); dma.linuxDmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) { Debug::log(LOG, "[core] dmabufFeedbackMainDevice"); RASSERT(!dma.gbm, "double dmabuf feedback"); dev_t device; assert(device_arr->size == sizeof(device)); memcpy(&device, device_arr->data, sizeof(device)); drmDevice* drmDev; RASSERT(drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) == 0, "unable to open main device?"); dma.gbmDevice = createGBMDevice(drmDev); drmFreeDevice(&drmDev); }); dma.linuxDmabuf->setModifier([this](CCZwpLinuxDmabufV1* r, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { dma.dmabufMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo}); }); } void CHyprlock::removeDmabufListener() { if (dma.linuxDmabufFeedback) { dma.linuxDmabufFeedback->sendDestroy(); dma.linuxDmabufFeedback.reset(); } if (dma.linuxDmabuf) { dma.linuxDmabuf->sendDestroy(); dma.linuxDmabuf.reset(); } } void CHyprlock::run() { m_sWaylandState.registry = makeShared((wl_proxy*)wl_display_get_registry(m_sWaylandState.display)); m_sWaylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) { const std::string IFACE = interface; Debug::log(LOG, " | got iface: {} v{}", IFACE, version); if (IFACE == zwp_linux_dmabuf_v1_interface.name) { if (version < 4) { Debug::log(ERR, "cannot use linux_dmabuf with ver < 4"); return; } dma.linuxDmabuf = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwp_linux_dmabuf_v1_interface, 4)); dma.linuxDmabufFeedback = makeShared(dma.linuxDmabuf->sendGetDefaultFeedback()); addDmabufListener(); } else if (IFACE == wl_seat_interface.name) { if (g_pSeatManager->registered()) { Debug::log(WARN, "Hyprlock does not support multi-seat configurations. Only binding to the first seat."); return; } g_pSeatManager->registerSeat(makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_seat_interface, 8))); } else if (IFACE == ext_session_lock_manager_v1_interface.name) m_sWaylandState.sessionLock = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &ext_session_lock_manager_v1_interface, 1)); else if (IFACE == wl_output_interface.name) { const auto POUTPUT = makeShared(); POUTPUT->create(POUTPUT, makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name); m_vOutputs.emplace_back(POUTPUT); } else if (IFACE == wp_cursor_shape_manager_v1_interface.name) g_pSeatManager->registerCursorShape( makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_cursor_shape_manager_v1_interface, 1))); else if (IFACE == wl_compositor_interface.name) m_sWaylandState.compositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_compositor_interface, 4)); else if (IFACE == wp_fractional_scale_manager_v1_interface.name) m_sWaylandState.fractional = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_fractional_scale_manager_v1_interface, 1)); else if (IFACE == wp_viewporter_interface.name) m_sWaylandState.viewporter = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_viewporter_interface, 1)); else if (IFACE == zwlr_screencopy_manager_v1_interface.name) m_sWaylandState.screencopy = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwlr_screencopy_manager_v1_interface, 3)); else if (IFACE == wl_shm_interface.name) m_sWaylandState.shm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_shm_interface, 1)); else return; Debug::log(LOG, " > Bound to {} v{}", IFACE, version); }); m_sWaylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) { Debug::log(LOG, " | removed iface {}", name); auto outputIt = std::ranges::find_if(m_vOutputs, [id = name](const auto& other) { return other->m_ID == id; }); if (outputIt != m_vOutputs.end()) { g_pRenderer->removeWidgetsFor((*outputIt)->m_ID); m_vOutputs.erase(outputIt); } // TODO: Recreating the rendering context like this fixes an issue with nvidia graphics when reconnecting monitors. // It only happens when there are no monitors left. // This is either an nvidia bug (probably egl-wayland) or it is a hyprlock bug. In any case, the goal is to remove this at some point! if (g_pEGL->m_isNvidia && m_vOutputs.empty()) { Debug::log(LOG, "NVIDIA Workaround: destroying rendering context to avoid crash on reconnect!"); g_pEGL.reset(); g_pRenderer.reset(); g_pEGL = makeUnique(m_sWaylandState.display); g_pRenderer = makeUnique(); g_pRenderer->warpOpacity(1.0); } }); wl_display_roundtrip(m_sWaylandState.display); if (!m_sWaylandState.sessionLock) { Debug::log(CRIT, "Couldn't bind to ext-session-lock-v1, does your compositor support it?"); exit(1); } // gather info about monitors wl_display_roundtrip(m_sWaylandState.display); g_pRenderer = makeUnique(); g_pAsyncResourceGatherer = makeUnique(); g_pAuth = makeUnique(); g_pAuth->start(); Debug::log(LOG, "Running on {}", m_sCurrentDesktop); if (!g_pHyprlock->m_bImmediateRender) { // Gather background resources and screencopy frames before locking the screen. // We need to do this because as soon as we lock the screen, workspaces frames can no longer be captured. It either won't work at all, or we will capture hyprlock itself. // Bypass with --immediate-render (can cause the background first rendering a solid color and missing or inaccurate screencopy frames) const auto MAXDELAYMS = 2000; // 2 Seconds const auto STARTGATHERTP = std::chrono::system_clock::now(); int fdcount = 1; pollfd pollfds[2]; pollfds[0] = { .fd = wl_display_get_fd(m_sWaylandState.display), .events = POLLIN, }; if (g_pAsyncResourceGatherer->gatheredEventfd.isValid()) { pollfds[1] = { .fd = g_pAsyncResourceGatherer->gatheredEventfd.get(), .events = POLLIN, }; fdcount++; } while (!g_pAsyncResourceGatherer->gathered) { wl_display_flush(m_sWaylandState.display); if (wl_display_prepare_read(m_sWaylandState.display) == 0) { if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) { RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); wl_display_cancel_read(m_sWaylandState.display); continue; } wl_display_read_events(m_sWaylandState.display); wl_display_dispatch_pending(m_sWaylandState.display); } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); wl_display_dispatch(m_sWaylandState.display); } if (std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) { Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS); break; } } Debug::log(LOG, "Resources gathered after {} milliseconds", std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count()); } // Failed to lock the session if (!acquireSessionLock()) { m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); g_pAuth->terminate(); exit(1); } const auto fingerprintAuth = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT); const auto dbusConn = (fingerprintAuth) ? ((CFingerprint*)fingerprintAuth.get())->getConnection() : nullptr; registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART); registerSignalAction(SIGUSR2, handleForceUpdateSignal); registerSignalAction(SIGRTMIN, handlePollTerminate); pollfd pollfds[2]; pollfds[0] = { .fd = wl_display_get_fd(m_sWaylandState.display), .events = POLLIN, }; if (dbusConn) { pollfds[1] = { .fd = dbusConn->getEventLoopPollData().fd, .events = POLLIN, }; } size_t fdcount = dbusConn ? 2 : 1; std::thread pollThr([this, &pollfds, fdcount]() { while (!m_bTerminate) { bool preparedToRead = wl_display_prepare_read(m_sWaylandState.display) == 0; int events = 0; if (preparedToRead) { events = poll(pollfds, fdcount, 5000); if (events < 0) { RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); wl_display_cancel_read(m_sWaylandState.display); continue; } for (size_t i = 0; i < fdcount; ++i) { RASSERT(!(pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i); } wl_display_read_events(m_sWaylandState.display); m_sLoopState.wlDispatched = false; } if (events > 0 || !preparedToRead) { Debug::log(TRACE, "[core] got poll event"); std::unique_lock lk(m_sLoopState.eventLoopMutex); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); m_sLoopState.wlDispatchCV.wait_for(lk, std::chrono::milliseconds(100), [this] { return m_sLoopState.wlDispatched; }); } } }); std::thread timersThr([this]() { while (!m_bTerminate) { // calc nearest thing m_sLoopState.timersMutex.lock(); float least = 10000; for (auto& t : m_vTimers) { const auto TIME = std::clamp(t->leftMs(), 1.f, INFINITY); least = std::min(TIME, least); } m_sLoopState.timersMutex.unlock(); std::unique_lock lk(m_sLoopState.timerRequestMutex); m_sLoopState.timerCV.wait_for(lk, std::chrono::milliseconds((int)least + 1), [this] { return m_sLoopState.timerEvent; }); m_sLoopState.timerEvent = false; // notify main std::lock_guard lg2(m_sLoopState.eventLoopMutex); Debug::log(TRACE, "timer thread firing"); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); } }); m_sLoopState.event = true; // let it process once g_pRenderer->startFadeIn(); while (!m_bTerminate) { std::unique_lock lk(m_sLoopState.eventRequestMutex); if (!m_sLoopState.event) m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; }); if (m_bTerminate) break; std::lock_guard lg(m_sLoopState.eventLoopMutex); m_sLoopState.event = false; wl_display_dispatch_pending(m_sWaylandState.display); wl_display_flush(m_sWaylandState.display); m_sLoopState.wlDispatched = true; m_sLoopState.wlDispatchCV.notify_all(); if (pollfds[1].revents & POLLIN /* dbus */) { while (dbusConn && dbusConn->processPendingEvent()) { ; } } // do timers m_sLoopState.timersMutex.lock(); auto timerscpy = m_vTimers; m_sLoopState.timersMutex.unlock(); std::vector> passed; for (auto& t : timerscpy) { if (t->passed() && !t->cancelled()) { t->call(t); passed.push_back(t); } if (t->cancelled()) passed.push_back(t); } m_sLoopState.timersMutex.lock(); std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); }); m_sLoopState.timersMutex.unlock(); passed.clear(); } const auto DPY = m_sWaylandState.display; m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); m_sWaylandState = {}; dma = {}; m_vOutputs.clear(); g_pSeatManager.reset(); g_pAsyncResourceGatherer.reset(); g_pRenderer.reset(); g_pEGL.reset(); wl_display_disconnect(DPY); pthread_kill(pollThr.native_handle(), SIGRTMIN); g_pAuth->terminate(); // wait for threads to exit cleanly to avoid a coredump pollThr.join(); timersThr.join(); Debug::log(LOG, "Reached the end, exiting"); } void CHyprlock::unlock() { if (!m_bLocked) { Debug::log(WARN, "Unlock called, but not locked yet. This can happen when dpms is off during the grace period."); return; } g_pRenderer->startFadeOut(true); renderAllOutputs(); } bool CHyprlock::isUnlocked() { return !m_bLocked; } void CHyprlock::clearPasswordBuffer() { if (m_sPasswordState.passBuffer.empty()) return; m_sPasswordState.passBuffer = ""; renderAllOutputs(); } void CHyprlock::renderOutput(const std::string& stringPort) { const auto MON = std::ranges::find_if(m_vOutputs, [stringPort](const auto& other) { return other->stringPort == stringPort; }); if (MON == m_vOutputs.end() || !*MON) return; const auto& PMONITOR = *MON; if (!PMONITOR->m_sessionLockSurface) return; PMONITOR->m_sessionLockSurface->render(); } void CHyprlock::renderAllOutputs() { for (auto& o : m_vOutputs) { if (!o->m_sessionLockSurface) continue; o->m_sessionLockSurface->render(); } } void CHyprlock::startKeyRepeat(xkb_keysym_t sym) { if (m_pKeyRepeatTimer) { m_pKeyRepeatTimer->cancel(); m_pKeyRepeatTimer.reset(); } if (g_pSeatManager->m_pXKBComposeState) xkb_compose_state_reset(g_pSeatManager->m_pXKBComposeState); if (m_iKeebRepeatDelay <= 0) return; m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatDelay), [sym](ASP self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr); } void CHyprlock::repeatKey(xkb_keysym_t sym) { if (m_iKeebRepeatRate <= 0) return; handleKeySym(sym, false); // This condition is for backspace and delete keys, but should also be ok for other keysyms since our buffer won't be empty anyways if (bool CONTINUE = m_sPasswordState.passBuffer.length() > 0; CONTINUE) m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatRate), [sym](ASP self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr); renderAllOutputs(); } void CHyprlock::onKey(uint32_t key, bool down) { if (isUnlocked()) return; if (down && std::chrono::system_clock::now() < m_tGraceEnds) { unlock(); return; } if (down && std::ranges::find(m_vPressedKeys, key) != m_vPressedKeys.end()) { Debug::log(ERR, "Invalid key down event (key already pressed?)"); return; } else if (!down && std::ranges::find(m_vPressedKeys, key) == m_vPressedKeys.end()) { Debug::log(ERR, "Invalid key down event (stray release event?)"); return; } if (down) m_vPressedKeys.push_back(key); else { std::erase(m_vPressedKeys, key); if (m_pKeyRepeatTimer) { m_pKeyRepeatTimer->cancel(); m_pKeyRepeatTimer.reset(); } } if (g_pAuth->checkWaiting()) { renderAllOutputs(); return; } if (g_pAuth->m_bDisplayFailText) g_pAuth->resetDisplayFail(); if (down) { m_bCapsLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); m_bNumLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED); m_bCtrl = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); const auto SYM = xkb_state_key_get_one_sym(g_pSeatManager->m_pXKBState, key + 8); enum xkb_compose_status composeStatus = XKB_COMPOSE_NOTHING; if (g_pSeatManager->m_pXKBComposeState) { xkb_compose_state_feed(g_pSeatManager->m_pXKBComposeState, SYM); composeStatus = xkb_compose_state_get_status(g_pSeatManager->m_pXKBComposeState); } handleKeySym(SYM, composeStatus == XKB_COMPOSE_COMPOSED); if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) // keys allowed to repeat startKeyRepeat(SYM); } else if (g_pSeatManager->m_pXKBComposeState && xkb_compose_state_get_status(g_pSeatManager->m_pXKBComposeState) == XKB_COMPOSE_COMPOSED) xkb_compose_state_reset(g_pSeatManager->m_pXKBComposeState); renderAllOutputs(); } void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) { const auto SYM = sym; if (SYM == XKB_KEY_Escape || (m_bCtrl && (SYM == XKB_KEY_u || SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_a))) { Debug::log(LOG, "Clearing password buffer"); m_sPasswordState.passBuffer = ""; } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { Debug::log(LOG, "Authenticating"); static const auto IGNOREEMPTY = g_pConfigManager->getValue("general:ignore_empty_input"); if (m_sPasswordState.passBuffer.empty() && *IGNOREEMPTY) { Debug::log(LOG, "Ignoring empty input"); return; } g_pAuth->submitInput(m_sPasswordState.passBuffer); } else if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) { if (m_sPasswordState.passBuffer.length() > 0) { // handle utf-8 while ((m_sPasswordState.passBuffer.back() & 0xc0) == 0x80) m_sPasswordState.passBuffer.pop_back(); m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1); } } else if (SYM == XKB_KEY_Caps_Lock) { m_bCapsLock = !m_bCapsLock; } else if (SYM == XKB_KEY_Num_Lock) { m_bNumLock = !m_bNumLock; } else { char buf[16] = {0}; int len = (composed) ? xkb_compose_state_get_utf8(g_pSeatManager->m_pXKBComposeState, buf, sizeof(buf)) /* nullbyte */ + 1 : xkb_keysym_to_utf8(SYM, buf, sizeof(buf)) /* already includes a nullbyte */; if (len > 1) m_sPasswordState.passBuffer += std::string{buf, len - 1}; } } void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) { if (!down) return; if (!m_focusedOutput.lock()) return; // TODO: add the UNLIKELY marco from Hyprland if (!m_focusedOutput->m_sessionLockSurface) return; const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale; const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface); for (const auto& widget : widgets) { if (widget->containsPoint(SCALEDPOS)) widget->onClick(button, down, pos); } } void CHyprlock::onHover(const Vector2D& pos) { if (!m_focusedOutput.lock()) return; if (!m_focusedOutput->m_sessionLockSurface) return; bool outputNeedsRedraw = false; bool cursorChanged = false; const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale; const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface); for (const auto& widget : widgets) { const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS); const bool HOVERED = widget->isHovered(); if (CONTAINSPOINT) { if (!HOVERED) { widget->setHover(true); widget->onHover(pos); outputNeedsRedraw = true; } if (!cursorChanged) cursorChanged = true; } else if (HOVERED) { widget->setHover(false); outputNeedsRedraw = true; } } if (!cursorChanged) g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); if (outputNeedsRedraw) m_focusedOutput->m_sessionLockSurface->render(); } bool CHyprlock::acquireSessionLock() { Debug::log(LOG, "Locking session"); m_sLockState.lock = makeShared(m_sWaylandState.sessionLock->sendLock()); if (!m_sLockState.lock) { Debug::log(ERR, "Failed to create a lock object!"); return false; } m_sLockState.lock->setLocked([this](CCExtSessionLockV1* r) { onLockLocked(); }); m_sLockState.lock->setFinished([this](CCExtSessionLockV1* r) { onLockFinished(); }); // roundtrip in case the compositor sends `finished` right away wl_display_roundtrip(m_sWaylandState.display); // recieved finished right away (probably already locked) if (m_bTerminate) return false; m_lockAquired = true; // create a session lock surface for exiting outputs for (auto& o : m_vOutputs) { if (!o->done) continue; o->createSessionLockSurface(); } return true; } void CHyprlock::releaseSessionLock() { Debug::log(LOG, "Unlocking session"); if (m_bTerminate) { Debug::log(ERR, "Unlock already happend?"); return; } if (!m_sLockState.lock) { Debug::log(ERR, "Unlock without a lock object!"); return; } if (!m_bLocked) { // Would be a protocol error to allow this Debug::log(ERR, "Trying to unlock the session, but never recieved the locked event!"); return; } m_sLockState.lock->sendUnlockAndDestroy(); m_sLockState.lock = nullptr; Debug::log(LOG, "Unlocked, exiting!"); m_bTerminate = true; m_bLocked = false; wl_display_roundtrip(m_sWaylandState.display); } void CHyprlock::onLockLocked() { Debug::log(LOG, "onLockLocked called"); m_bLocked = true; } void CHyprlock::onLockFinished() { Debug::log(LOG, "onLockFinished called. Seems we got yeeten. Is another lockscreen running?"); if (!m_sLockState.lock) { Debug::log(ERR, "onLockFinished without a lock object!"); return; } if (m_bLocked) // The `finished` event specifies that whenever the `locked` event has been recieved and the compositor sends `finished`, // `unlock_and_destroy` should be called by the client. // This does not mean the session gets unlocked! That is ultimately the responsiblity of the compositor. m_sLockState.lock->sendUnlockAndDestroy(); else m_sLockState.lock.reset(); m_sLockState.lock = nullptr; m_bTerminate = true; } SP CHyprlock::getSessionLockMgr() { return m_sWaylandState.sessionLock; } SP CHyprlock::getSessionLock() { return m_sLockState.lock; } SP CHyprlock::getCompositor() { return m_sWaylandState.compositor; } wl_display* CHyprlock::getDisplay() { return m_sWaylandState.display; } SP CHyprlock::getFractionalMgr() { return m_sWaylandState.fractional; } SP CHyprlock::getViewporter() { return m_sWaylandState.viewporter; } size_t CHyprlock::getPasswordBufferLen() { return m_sPasswordState.passBuffer.length(); } size_t CHyprlock::getPasswordBufferDisplayLen() { // Counts utf-8 codepoints in the buffer. A byte is counted if it does not match 0b10xxxxxx. return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; }); } ASP CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force) { std::lock_guard lg(m_sLoopState.timersMutex); const auto T = m_vTimers.emplace_back(makeAtomicShared(timeout, cb_, data, force)); m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); return T; } std::vector> CHyprlock::getTimers() { return m_vTimers; } void CHyprlock::enqueueForceUpdateTimers() { addTimer( std::chrono::milliseconds(1), [](ASP self, void* data) { for (auto& t : g_pHyprlock->getTimers()) { if (t->canForceUpdate()) { t->call(t); t->cancel(); } } }, nullptr, false); } SP CHyprlock::getScreencopy() { return m_sWaylandState.screencopy; } SP CHyprlock::getShm() { return m_sWaylandState.shm; } hyprlock-0.9.2/src/core/hyprlock.hpp000066400000000000000000000133141506744673100174570ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "wayland.hpp" #include "ext-session-lock-v1.hpp" #include "fractional-scale-v1.hpp" #include "wlr-screencopy-unstable-v1.hpp" #include "linux-dmabuf-v1.hpp" #include "viewporter.hpp" #include "Output.hpp" #include "Seat.hpp" #include "CursorShape.hpp" #include "Timer.hpp" #include #include #include #include #include #include #include #include struct SDMABUFModifier { uint32_t fourcc = 0; uint64_t mod = 0; }; class CHyprlock { public: CHyprlock(const std::string& wlDisplay, const bool immediateRender, const int gracePeriod); ~CHyprlock(); void run(); void unlock(); bool isUnlocked(); ASP addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); void enqueueForceUpdateTimers(); void onLockLocked(); void onLockFinished(); bool acquireSessionLock(); void releaseSessionLock(); void onKey(uint32_t key, bool down); void onClick(uint32_t button, bool down, const Vector2D& pos); void onHover(const Vector2D& pos); void startKeyRepeat(xkb_keysym_t sym); void repeatKey(xkb_keysym_t sym); void handleKeySym(xkb_keysym_t sym, bool compose); void onPasswordCheckTimer(); void clearPasswordBuffer(); bool passwordCheckWaiting(); std::optional passwordLastFailReason(); void renderOutput(const std::string& stringPort); void renderAllOutputs(); size_t getPasswordBufferLen(); size_t getPasswordBufferDisplayLen(); SP getSessionLockMgr(); SP getSessionLock(); SP getCompositor(); wl_display* getDisplay(); SP getFractionalMgr(); SP getViewporter(); SP getScreencopy(); SP getShm(); int32_t m_iKeebRepeatRate = 25; int32_t m_iKeebRepeatDelay = 600; xkb_layout_index_t m_uiActiveLayout = 0; bool m_bTerminate = false; bool m_lockAquired = false; bool m_bLocked = false; bool m_bCapsLock = false; bool m_bNumLock = false; bool m_bCtrl = false; bool m_bImmediateRender = false; std::string m_sCurrentDesktop = ""; // std::chrono::system_clock::time_point m_tGraceEnds; Vector2D m_vLastEnterCoords = {}; WP m_focusedOutput; Vector2D m_vMouseLocation = {}; ASP m_pKeyRepeatTimer = nullptr; std::vector> m_vOutputs; std::vector> getTimers(); struct { SP linuxDmabuf = nullptr; SP linuxDmabufFeedback = nullptr; gbm_bo* gbm = nullptr; gbm_device* gbmDevice = nullptr; void* formatTable = nullptr; size_t formatTableSize = 0; bool deviceUsed = false; std::vector dmabufMods; } dma; gbm_device* createGBMDevice(drmDevice* dev); void addDmabufListener(); void removeDmabufListener(); private: struct { wl_display* display = nullptr; SP registry = nullptr; SP sessionLock = nullptr; SP compositor = nullptr; SP fractional = nullptr; SP viewporter = nullptr; SP screencopy = nullptr; SP shm = nullptr; } m_sWaylandState; struct { SP lock = nullptr; } m_sLockState; struct { std::string passBuffer = ""; size_t failedAttempts = 0; bool displayFailText = false; } m_sPasswordState; struct { std::mutex timersMutex; std::mutex eventRequestMutex; std::mutex eventLoopMutex; std::condition_variable loopCV; bool event = false; std::condition_variable wlDispatchCV; bool wlDispatched = false; std::condition_variable timerCV; std::mutex timerRequestMutex; bool timerEvent = false; } m_sLoopState; std::vector> m_vTimers; std::vector m_vPressedKeys; }; inline UP g_pHyprlock; hyprlock-0.9.2/src/defines.hpp000066400000000000000000000007041506744673100163100ustar00rootroot00000000000000#pragma once #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprgraphics; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer #define ASP CAtomicSharedPointer #define AWP CAtomicWeakPointer typedef int64_t OUTPUTID; constexpr OUTPUTID OUTPUT_INVALID = -1; hyprlock-0.9.2/src/helpers/000077500000000000000000000000001506744673100156235ustar00rootroot00000000000000hyprlock-0.9.2/src/helpers/AnimatedVariable.hpp000066400000000000000000000035651506744673100215350ustar00rootroot00000000000000#pragma once #include #include "Color.hpp" #include "Math.hpp" #include "../defines.hpp" #include "../config/ConfigDataValues.hpp" enum eAnimatedVarType { AVARTYPE_INVALID = -1, AVARTYPE_FLOAT, AVARTYPE_VECTOR, AVARTYPE_COLOR, AVARTYPE_GRADIENT }; // Utility to bind a type with its corresponding eAnimatedVarType template // NOLINTNEXTLINE(readability-identifier-naming) struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_INVALID; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_FLOAT; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_VECTOR; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_COLOR; }; template <> struct STypeToAnimatedVarType_t { static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT; }; template inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t::value; // Utility to define a concept as a list of possible type template concept OneOf = (... or std::same_as); // Concept to describe which type can be placed into CAnimatedVariable // This is mainly to get better errors if we put a type that's not supported // Otherwise template errors are ugly template concept Animable = OneOf; struct SAnimationContext {}; template using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable; template using PHLANIMVAR = SP>; template using PHLANIMVARREF = WP>; hyprlock-0.9.2/src/helpers/Color.cpp000066400000000000000000000026511506744673100174110ustar00rootroot00000000000000#include "Color.hpp" #define ALPHA(c) ((double)(((c) >> 24) & 0xff) / 255.0) #define RED(c) ((double)(((c) >> 16) & 0xff) / 255.0) #define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0) #define BLUE(c) ((double)(((c)) & 0xff) / 255.0) CHyprColor::CHyprColor() { ; } CHyprColor::CHyprColor(float r_, float g_, float b_, float a_) : r(r_), g(g_), b(b_), a(a_) { okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab(); } CHyprColor::CHyprColor(uint64_t hex) : r(RED(hex)), g(GREEN(hex)), b(BLUE(hex)), a(ALPHA(hex)) { okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab(); } CHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) : a(a_) { const auto SRGB = color.asRgb(); r = SRGB.r; g = SRGB.g; b = SRGB.b; okLab = color.asOkLab(); } uint32_t CHyprColor::getAsHex() const { return ((uint32_t)(a * 255.f) * 0x1000000) + ((uint32_t)(r * 255.f) * 0x10000) + ((uint32_t)(g * 255.f) * 0x100) + ((uint32_t)(b * 255.f) * 0x1); } Hyprgraphics::CColor::SSRGB CHyprColor::asRGB() const { return {.r = r, .g = g, .b = b}; } Hyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const { return okLab; } Hyprgraphics::CColor::SHSL CHyprColor::asHSL() const { return Hyprgraphics::CColor(okLab).asHSL(); } CHyprColor CHyprColor::stripA() const { return {r, g, b, 1.F}; } hyprlock-0.9.2/src/helpers/Color.hpp000066400000000000000000000023211506744673100174100ustar00rootroot00000000000000#pragma once #include #include "../helpers/Log.hpp" #include class CHyprColor { public: CHyprColor(); CHyprColor(float r, float g, float b, float a); CHyprColor(const Hyprgraphics::CColor& col, float a); CHyprColor(uint64_t); // AR32 uint32_t getAsHex() const; Hyprgraphics::CColor::SSRGB asRGB() const; Hyprgraphics::CColor::SOkLab asOkLab() const; Hyprgraphics::CColor::SHSL asHSL() const; CHyprColor stripA() const; // bool operator==(const CHyprColor& c2) const { return c2.r == r && c2.g == g && c2.b == b && c2.a == a; } // stubs for the AnimationMgr CHyprColor operator-(const CHyprColor& c2) const { RASSERT(false, "CHyprColor: - is a STUB"); return {}; } CHyprColor operator+(const CHyprColor& c2) const { RASSERT(false, "CHyprColor: + is a STUB"); return {}; } CHyprColor operator*(const float& c2) const { RASSERT(false, "CHyprColor: * is a STUB"); return {}; } double r = 0, g = 0, b = 0, a = 0; private: Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation }; hyprlock-0.9.2/src/helpers/Log.hpp000066400000000000000000000041461506744673100170620ustar00rootroot00000000000000#pragma once #include #include #include enum eLogLevel { TRACE = 0, INFO, LOG, WARN, ERR, CRIT, NONE }; #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ Debug::log(CRIT, "\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); })().c_str()); \ std::abort(); \ } #define ASSERT(expr) RASSERT(expr, "?") namespace Debug { constexpr const char* logLevelString(eLogLevel level) { switch (level) { case TRACE: return "TRACE"; break; case INFO: return "INFO"; break; case LOG: return "LOG"; break; case WARN: return "WARN"; break; case ERR: return "ERR"; break; case CRIT: return "CRITICAL"; break; default: return "??"; } } inline bool quiet = false; inline bool verbose = false; template void log(eLogLevel level, const std::string& fmt, Args&&... args) { if (!verbose && level == TRACE) return; if (quiet) return; if (level != NONE) { std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...))); } } };hyprlock-0.9.2/src/helpers/Math.cpp000066400000000000000000000024161506744673100172230ustar00rootroot00000000000000#include "Math.hpp" Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { switch (t) { case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180; case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90; case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270; case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED; case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180; case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270; case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90; default: break; } return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; } wl_output_transform invertTransform(wl_output_transform tr) { if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) tr = (wl_output_transform)(tr ^ (int)WL_OUTPUT_TRANSFORM_180); return tr; } hyprlock-0.9.2/src/helpers/Math.hpp000066400000000000000000000004741506744673100172320ustar00rootroot00000000000000#pragma once #include #include #include #include using namespace Hyprutils::Math; eTransform wlTransformToHyprutils(wl_output_transform t); wl_output_transform invertTransform(wl_output_transform tr); hyprlock-0.9.2/src/helpers/MiscFunctions.cpp000066400000000000000000000137551506744673100211260ustar00rootroot00000000000000#include #include #include #include #include "MiscFunctions.hpp" #include "Log.hpp" #include #include #include using namespace Hyprutils::String; using namespace Hyprutils::OS; std::string absolutePath(const std::string& rawpath, const std::string& currentDir) { std::filesystem::path path(rawpath); // Handling where rawpath starts with '~' if (!rawpath.empty() && rawpath[0] == '~') { static const char* const ENVHOME = getenv("HOME"); path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); } // Handling e.g. ./, ../ if (path.is_relative()) { return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path); } else { return std::filesystem::weakly_canonical(path); } } int64_t configStringToInt(const std::string& VALUE) { auto parseHex = [](const std::string& value) -> int64_t { try { size_t position; auto result = stoll(value, &position, 16); if (position == value.size()) return result; } catch (const std::exception&) {} throw std::invalid_argument("invalid hex " + value); }; if (VALUE.starts_with("0x")) { // Values with 0x are hex return parseHex(VALUE); } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) { const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6)); // try doing it the comma way first if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 3) { // cool std::string rolling = VALUEWITHOUTFUNC; auto r = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); rolling = rolling.substr(rolling.find(',') + 1); auto g = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); rolling = rolling.substr(rolling.find(',') + 1); auto b = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); rolling = rolling.substr(rolling.find(',') + 1); uint8_t a = 0; try { a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f); } catch (std::exception& e) { throw std::invalid_argument("failed parsing " + VALUEWITHOUTFUNC); } return (a * (Hyprlang::INT)0x1000000) + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b; } else if (VALUEWITHOUTFUNC.length() == 8) { const auto RGBA = parseHex(VALUEWITHOUTFUNC); // now we need to RGBA -> ARGB. The config holds ARGB only. return (RGBA >> 8) + (0x1000000 * (RGBA & 0xFF)); } throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values"); } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) { const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5)); // try doing it the comma way first if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 2) { // cool std::string rolling = VALUEWITHOUTFUNC; auto r = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); rolling = rolling.substr(rolling.find(',') + 1); auto g = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); rolling = rolling.substr(rolling.find(',') + 1); auto b = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); return (Hyprlang::INT)0xFF000000 + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b; } else if (VALUEWITHOUTFUNC.length() == 6) { return parseHex(VALUEWITHOUTFUNC) + 0xFF000000; } throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes) or 3 comma separated values"); } else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) { return 1; } else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) { return 0; } if (VALUE.empty() || !isNumber(VALUE, false)) throw std::invalid_argument("cannot parse \"" + VALUE + "\" as an int."); try { const auto RES = std::stoll(VALUE); return RES; } catch (std::exception& e) { throw std::invalid_argument(std::string{"stoll threw: "} + e.what()); } return 0; } int createPoolFile(size_t size, std::string& name) { const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR"); if (!XDGRUNTIMEDIR) { Debug::log(CRIT, "XDG_RUNTIME_DIR not set!"); return -1; } name = std::string(XDGRUNTIMEDIR) + "/.hyprlock_sc_XXXXXX"; const auto FD = mkstemp((char*)name.c_str()); if (FD < 0) { Debug::log(CRIT, "createPoolFile: fd < 0"); return -1; } // set cloexec long flags = fcntl(FD, F_GETFD); if (flags == -1) { close(FD); return -1; } if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) { close(FD); Debug::log(CRIT, "createPoolFile: fcntl < 0"); return -1; } if (ftruncate(FD, size) < 0) { close(FD); Debug::log(CRIT, "createPoolFile: ftruncate < 0"); return -1; } return FD; } std::string spawnSync(const std::string& cmd) { CProcess proc("/bin/sh", {"-c", cmd}); if (!proc.runSync()) { Debug::log(ERR, "Failed to run \"{}\"", cmd); return ""; } if (!proc.stdErr().empty()) Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr()); return proc.stdOut(); } void spawnAsync(const std::string& cmd) { CProcess proc("/bin/sh", {"-c", cmd}); if (!proc.runAsync()) Debug::log(ERR, "Failed to start \"{}\"", cmd); } hyprlock-0.9.2/src/helpers/MiscFunctions.hpp000066400000000000000000000005661506744673100211270ustar00rootroot00000000000000#pragma once #include #include #include std::string absolutePath(const std::string&, const std::string&); int64_t configStringToInt(const std::string& VALUE); int createPoolFile(size_t size, std::string& name); std::string spawnSync(const std::string& cmd); void spawnAsync(const std::string& cmd); hyprlock-0.9.2/src/main.cpp000066400000000000000000000114071506744673100156140ustar00rootroot00000000000000 #include "config/ConfigManager.hpp" #include "core/hyprlock.hpp" #include "helpers/Log.hpp" #include "core/AnimationManager.hpp" #include #include void help() { std::println("Usage: hyprlock [options]\n\n" "Options:\n" " -v, --verbose - Enable verbose logging\n" " -q, --quiet - Disable logging\n" " -c FILE, --config FILE - Specify config file to use\n" " --display NAME - Specify the Wayland display to connect to\n" " --grace SECONDS - Set grace period in seconds before requiring authentication\n" " --immediate-render - Do not wait for resources before drawing the background\n" " --no-fade-in - Disable the fade-in animation when the lock screen appears\n" " -V, --version - Show version information\n" " -h, --help - Show this help message"); } std::optional parseArg(const std::vector& args, const std::string& flag, std::size_t& i) { if (i + 1 < args.size()) { return args[++i]; } else { std::println(stderr, "Error: Missing value for {} option.", flag); return std::nullopt; } } static void printVersion() { constexpr bool ISTAGGEDRELEASE = std::string_view(HYPRLOCK_COMMIT) == HYPRLOCK_VERSION_COMMIT; if (ISTAGGEDRELEASE) std::println("Hyprlock version v{}", HYPRLOCK_VERSION); else std::println("Hyprlock version v{} (commit {})", HYPRLOCK_VERSION, HYPRLOCK_COMMIT); } int main(int argc, char** argv, char** envp) { std::string configPath; std::string wlDisplay; bool immediateRender = false; bool noFadeIn = false; int graceSeconds = 0; std::vector args(argv, argv + argc); for (std::size_t i = 1; i < args.size(); ++i) { const std::string arg = argv[i]; if (arg == "--help" || arg == "-h") { help(); return 0; } if (arg == "--version" || arg == "-V") { printVersion(); return 0; } if (arg == "--verbose" || arg == "-v") Debug::verbose = true; else if (arg == "--quiet" || arg == "-q") Debug::quiet = true; else if ((arg == "--config" || arg == "-c") && i + 1 < (std::size_t)argc) { if (auto value = parseArg(args, arg, i); value) configPath = *value; else return 1; } else if (arg == "--display" && i + 1 < (std::size_t)argc) { if (auto value = parseArg(args, arg, i); value) wlDisplay = *value; else return 1; } else if (arg == "--grace" && i + 1 < (std::size_t)argc) { if (auto value = parseArg(args, arg, i); value) { try { graceSeconds = std::stoi(*value); if (graceSeconds < 0) { std::println(stderr, "Error: Grace period must be non-negative."); return 1; } } catch (const std::exception&) { std::println(stderr, "Error: Invalid grace period value: {}", *value); return 1; } } else return 1; } else if (arg == "--immediate") { graceSeconds = 0; Debug::log(WARN, R"("--immediate" is deprecated. Use the "--grace" option instead.)"); } else if (arg == "--immediate-render") immediateRender = true; else if (arg == "--no-fade-in") noFadeIn = true; else { std::println(stderr, "Unknown option: {}", arg); help(); return 1; } } printVersion(); g_pAnimationManager = makeUnique(); try { g_pConfigManager = makeUnique(configPath); g_pConfigManager->init(); } catch (const std::exception& ex) { Debug::log(CRIT, "ConfigManager threw: {}", ex.what()); if (std::string(ex.what()).contains("File does not exist")) Debug::log(NONE, " Make sure you have a config."); return 1; } if (noFadeIn) g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default"); try { g_pHyprlock = makeUnique(wlDisplay, immediateRender, graceSeconds); g_pHyprlock->run(); } catch (const std::exception& ex) { Debug::log(CRIT, "Hyprlock threw: {}", ex.what()); return 1; } return 0; } hyprlock-0.9.2/src/renderer/000077500000000000000000000000001506744673100157675ustar00rootroot00000000000000hyprlock-0.9.2/src/renderer/AsyncResourceGatherer.cpp000066400000000000000000000331011506744673100227400ustar00rootroot00000000000000#include "AsyncResourceGatherer.hpp" #include "../config/ConfigManager.hpp" #include "../core/Egl.hpp" #include "../core/hyprlock.hpp" #include "../helpers/Color.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include #include #include #include #include #include #include using namespace Hyprgraphics; using namespace Hyprutils::OS; CAsyncResourceGatherer::CAsyncResourceGatherer() { if (g_pHyprlock->getScreencopy()) enqueueScreencopyFrames(); initialGatherThread = std::thread([this]() { this->gather(); }); asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); }); gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)}; if (!gatheredEventfd.isValid()) Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno)); } CAsyncResourceGatherer::~CAsyncResourceGatherer() { notify(); await(); } void CAsyncResourceGatherer::enqueueScreencopyFrames() { if (g_pHyprlock->m_vOutputs.empty()) return; static const auto ANIMATIONSENABLED = g_pConfigManager->getValue("animations:enabled"); const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn"); const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut"); const bool FADENEEDSSC = *ANIMATIONSENABLED && ((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled (FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled)); const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { // return w.type == "background" && std::string{std::any_cast(w.values.at("path"))} == "screenshot"; }); if (!BGSCREENSHOT && !FADENEEDSSC) { Debug::log(LOG, "Skipping screencopy"); return; } for (const auto& MON : g_pHyprlock->m_vOutputs) { scframes.emplace_back(makeUnique(MON)); } } ASP CAsyncResourceGatherer::getAssetByID(const std::string& id) { if (id.contains(CScreencopyFrame::RESOURCEIDPREFIX)) { for (auto& frame : scframes) { if (id == frame->m_resourceID) return frame->m_asset->ready ? frame->m_asset : nullptr; } return nullptr; } for (auto& a : assets) { if (a.first == id) return a.second; } if (apply()) { for (auto& a : assets) { if (a.first == id) return a.second; } }; return nullptr; } static SP getCairoSurfaceFromImageFile(const std::filesystem::path& path) { auto image = CImage(path); if (!image.success()) { Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError()); return nullptr; } return image.cairoSurface(); } void CAsyncResourceGatherer::gather() { const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); g_pEGL->makeCurrent(nullptr); // gather resources to preload // clang-format off int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) { return w.type == "background" || w.type == "image"; }); // clang-format on progress = 0; for (auto& c : CWIDGETS) { if (c.type == "background" || c.type == "image") { #if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100 progress = progress + 1.0 / (preloads + 1.0); #else progress += 1.0 / (preloads + 1.0); #endif std::string path = std::any_cast(c.values.at("path")); if (path.empty() || path == "screenshot") continue; std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; // render the image directly, since we are in a seperate thread CAsyncResourceGatherer::SPreloadRequest rq; rq.type = CAsyncResourceGatherer::TARGET_IMAGE; rq.asset = path; rq.id = id; renderImage(rq); } } // TODO: Wake this thread when all scframes are done instead of busy waiting. while (!g_pHyprlock->m_bTerminate && std::ranges::any_of(scframes, [](const auto& d) { return !d->m_asset->ready; })) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // We are done with screencopy. Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->removeDmabufListener(); }, nullptr); gathered = true; // wake hyprlock from poll if (gatheredEventfd.isValid()) eventfd_write(gatheredEventfd.get(), 1); } bool CAsyncResourceGatherer::apply() { preloadTargetsMutex.lock(); if (preloadTargets.empty()) { preloadTargetsMutex.unlock(); return false; } auto currentPreloadTargets = preloadTargets; preloadTargets.clear(); preloadTargetsMutex.unlock(); for (auto& t : currentPreloadTargets) { if (t.type == TARGET_IMAGE) { if (!assets.contains(t.id)) { assets[t.id] = makeAtomicShared(); } const auto ASSET = assets[t.id]; const cairo_status_t SURFACESTATUS = (cairo_status_t)t.cairosurface->status(); const auto CAIROFORMAT = cairo_image_surface_get_format(t.cairosurface->cairo()); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS)); ASSET->texture.m_iType = TEXTURE_INVALID; } ASSET->texture.m_vSize = t.size; ASSET->texture.allocate(); glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); } glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data); cairo_destroy((cairo_t*)t.cairo); t.cairosurface.reset(); } else Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type); } return true; } void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; target.id = rq.id; std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, "")); const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH); if (!CAIROISURFACE) { Debug::log(ERR, "renderImage: No cairo surface!"); return; } const auto CAIRO = cairo_create(CAIROISURFACE->cairo()); cairo_scale(CAIRO, 1, 1); target.cairo = CAIRO; target.cairosurface = CAIROISURFACE; target.data = CAIROISURFACE->data(); target.size = CAIROISURFACE->size(); std::lock_guard lg{preloadTargetsMutex}; preloadTargets.push_back(target); } void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; /* text is just an image lol */ target.id = rq.id; const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast(rq.props.at("font_size")) : 16; const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0); const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast(rq.props.at("font_family")) : "Sans"; const bool ISCMD = rq.props.contains("cmd") ? std::any_cast(rq.props.at("cmd")) : false; static const auto TRIM = g_pConfigManager->getValue("general:text_trim"); std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset; if (*TRIM) { text.erase(0, text.find_first_not_of(" \n\r\t")); text.erase(text.find_last_not_of(" \n\r\t") + 1); } auto CAIROSURFACE = makeShared(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */)); auto CAIRO = cairo_create(CAIROSURFACE->cairo()); // draw title using Pango PangoLayout* layout = pango_cairo_create_layout(CAIRO); PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str()); pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); if (rq.props.contains("text_align")) { const std::string TEXTALIGN = std::any_cast(rq.props.at("text_align")); PangoAlignment align = PANGO_ALIGN_LEFT; if (TEXTALIGN == "center") align = PANGO_ALIGN_CENTER; else if (TEXTALIGN == "right") align = PANGO_ALIGN_RIGHT; pango_layout_set_alignment(layout, align); } PangoAttrList* attrList = nullptr; GError* gError = nullptr; char* buf = nullptr; if (pango_parse_markup(text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) pango_layout_set_text(layout, buf, -1); else { Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message); g_error_free(gError); pango_layout_set_text(layout, text.c_str(), -1); } if (!attrList) attrList = pango_attr_list_new(); if (buf) free(buf); pango_attr_list_insert(attrList, pango_attr_scale_new(1)); pango_layout_set_attributes(layout, attrList); pango_attr_list_unref(attrList); int layoutWidth, layoutHeight; pango_layout_get_size(layout, &layoutWidth, &layoutHeight); // TODO: avoid this? cairo_destroy(CAIRO); CAIROSURFACE = makeShared(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE)); CAIRO = cairo_create(CAIROSURFACE->cairo()); // clear the pixmap cairo_save(CAIRO); cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); cairo_paint(CAIRO); cairo_restore(CAIRO); // render the thing cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a); cairo_move_to(CAIRO, 0, 0); pango_cairo_show_layout(CAIRO, layout); g_object_unref(layout); cairo_surface_flush(CAIROSURFACE->cairo()); target.cairo = CAIRO; target.cairosurface = CAIROSURFACE; target.data = CAIROSURFACE->data(); target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE}; std::lock_guard lg{preloadTargetsMutex}; preloadTargets.push_back(target); } void CAsyncResourceGatherer::asyncAssetSpinLock() { while (!g_pHyprlock->m_bTerminate) { std::unique_lock lk(asyncLoopState.requestsMutex); if (!asyncLoopState.pending) // avoid a lock if a thread managed to request something already since we .unlock()ed asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events asyncLoopState.pending = false; if (asyncLoopState.requests.empty()) { lk.unlock(); continue; } auto requests = asyncLoopState.requests; asyncLoopState.requests.clear(); lk.unlock(); // process requests for (auto& r : requests) { Debug::log(TRACE, "Processing requested resourceID {}", r.id); if (r.type == TARGET_TEXT) { renderText(r); } else if (r.type == TARGET_IMAGE) { renderImage(r); } else { Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type); continue; } // plant timer for callback if (r.callback) g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr); } } } void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) { Debug::log(TRACE, "Requesting label resource {}", request.id); std::lock_guard lg(asyncLoopState.requestsMutex); asyncLoopState.requests.push_back(request); asyncLoopState.pending = true; asyncLoopState.requestsCV.notify_all(); } void CAsyncResourceGatherer::unloadAsset(ASP asset) { std::erase_if(assets, [asset](const auto& a) { return a.second == asset; }); } void CAsyncResourceGatherer::notify() { std::lock_guard lg(asyncLoopState.requestsMutex); asyncLoopState.requests.clear(); asyncLoopState.pending = true; asyncLoopState.requestsCV.notify_all(); } void CAsyncResourceGatherer::await() { if (initialGatherThread.joinable()) initialGatherThread.join(); if (asyncLoopThread.joinable()) asyncLoopThread.join(); } hyprlock-0.9.2/src/renderer/AsyncResourceGatherer.hpp000066400000000000000000000054121506744673100227510ustar00rootroot00000000000000#pragma once #include "Screencopy.hpp" #include "../defines.hpp" #include #include #include #include #include #include #include "Shared.hpp" #include #include class CAsyncResourceGatherer { public: CAsyncResourceGatherer(); ~CAsyncResourceGatherer(); std::atomic gathered = false; Hyprutils::OS::CFileDescriptor gatheredEventfd; std::atomic progress = 0; /* only call from ogl thread */ ASP getAssetByID(const std::string& id); bool apply(); enum eTargetType { TARGET_IMAGE = 0, TARGET_TEXT }; struct SPreloadRequest { eTargetType type; std::string asset; std::string id; std::unordered_map props; // optional. Callbacks will be dispatched from the main thread, // so wayland/gl calls are OK. // will fire once the resource is fully loaded and ready. std::function callback = nullptr; }; void requestAsyncAssetPreload(const SPreloadRequest& request); void unloadAsset(ASP asset); private: void notify(); void await(); std::thread asyncLoopThread; std::thread initialGatherThread; void asyncAssetSpinLock(); void renderText(const SPreloadRequest& rq); void renderImage(const SPreloadRequest& rq); struct { std::condition_variable requestsCV; std::mutex requestsMutex; std::vector requests; bool pending = false; bool busy = false; } asyncLoopState; struct SPreloadTarget { eTargetType type = TARGET_IMAGE; std::string id = ""; void* data = nullptr; void* cairo = nullptr; SP cairosurface; Vector2D size; }; std::vector> scframes; std::vector preloadTargets; std::mutex preloadTargetsMutex; std::unordered_map> assets; void gather(); void enqueueScreencopyFrames(); }; inline UP g_pAsyncResourceGatherer; hyprlock-0.9.2/src/renderer/Framebuffer.cpp000066400000000000000000000101461506744673100207210ustar00rootroot00000000000000#include "Framebuffer.hpp" #include "../helpers/Log.hpp" #include #include #include static uint32_t drmFormatToGL(uint32_t drm) { switch (drm) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; default: return GL_RGBA; } return GL_RGBA; } static uint32_t glFormatToType(uint32_t gl) { return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; } bool CFramebuffer::alloc(int w, int h, bool highres) { bool firstAlloc = false; uint32_t glFormat = highres ? GL_RGBA16F : drmFormatToGL(DRM_FORMAT_XRGB2101010); // TODO: revise only 10b when I find a way to figure out without sc whether display is 10b uint32_t glType = highres ? GL_FLOAT : glFormatToType(glFormat); if (m_iFb == (uint32_t)-1) { firstAlloc = true; glGenFramebuffers(1, &m_iFb); } if (m_cTex.m_iTexID == 0) { firstAlloc = true; glGenTextures(1, &m_cTex.m_iTexID); glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); m_cTex.m_vSize = {w, h}; } if (firstAlloc || m_vSize != Vector2D(w, h)) { glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0); if (m_pStencilTex) { glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); } auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { Debug::log(ERR, "Framebuffer incomplete, couldn't create! (FB status: {})", status); abort(); } Debug::log(TRACE, "Framebuffer created, status {}", status); } glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); m_vSize = Vector2D(w, h); return true; } void CFramebuffer::addStencil() { if (!m_pStencilTex) { Debug::log(ERR, "No stencil texture allocated."); return; } glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo! (FB status: {})", status); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } void CFramebuffer::bind() const { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iFb); glViewport(0, 0, m_vSize.x, m_vSize.y); } void CFramebuffer::destroyBuffer() { if (m_iFb != (uint32_t)-1 && m_iFb) glDeleteFramebuffers(1, &m_iFb); if (m_cTex.m_iTexID) glDeleteTextures(1, &m_cTex.m_iTexID); if (m_pStencilTex && m_pStencilTex->m_iTexID) glDeleteTextures(1, &m_pStencilTex->m_iTexID); m_cTex.m_iTexID = 0; m_iFb = -1; m_vSize = Vector2D(); m_pStencilTex = nullptr; } CFramebuffer::~CFramebuffer() { destroyBuffer(); } bool CFramebuffer::isAllocated() const { return m_iFb != (GLuint)-1; } hyprlock-0.9.2/src/renderer/Framebuffer.hpp000066400000000000000000000011241506744673100207220ustar00rootroot00000000000000#pragma once #include "../helpers/Math.hpp" #include #include "Texture.hpp" class CFramebuffer { public: ~CFramebuffer(); bool alloc(int w, int h, bool highres = false); void addStencil(); void bind() const; void destroyBuffer(); bool isAllocated() const; Vector2D m_vSize; CTexture m_cTex; GLuint m_iFb = -1; CTexture* m_pStencilTex = nullptr; CFramebuffer& operator=(CFramebuffer&&) = delete; CFramebuffer& operator=(const CFramebuffer&) = delete; }; hyprlock-0.9.2/src/renderer/Renderer.cpp000066400000000000000000000631571506744673100202550ustar00rootroot00000000000000#include "Renderer.hpp" #include "Shaders.hpp" #include "Screencopy.hpp" #include "../config/ConfigManager.hpp" #include "../core/AnimationManager.hpp" #include "../core/Egl.hpp" #include "../core/Output.hpp" #include "../core/hyprlock.hpp" #include "../helpers/Color.hpp" #include "../helpers/Log.hpp" #include #include #include #include #include "widgets/PasswordInputField.hpp" #include "widgets/Background.hpp" #include "widgets/Label.hpp" #include "widgets/Image.hpp" #include "widgets/Shape.hpp" inline const float fullVerts[] = { 1, 0, // top right 0, 0, // top left 1, 1, // bottom right 0, 1, // bottom left }; static GLuint compileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, &shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); return shader; } static GLuint createProgram(const std::string& vert, const std::string& frag) { auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert); RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert); auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag); RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag); 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); RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); return prog; } static void glMessageCallbackA(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { if (type != GL_DEBUG_TYPE_ERROR) return; Debug::log(LOG, "[gl] {}", (const char*)message); } CRenderer::CRenderer() { g_pEGL->makeCurrent(nullptr); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(glMessageCallbackA, nullptr); GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC); rectShader.program = prog; rectShader.proj = glGetUniformLocation(prog, "proj"); rectShader.color = glGetUniformLocation(prog, "color"); rectShader.posAttrib = glGetAttribLocation(prog, "pos"); rectShader.topLeft = glGetUniformLocation(prog, "topLeft"); rectShader.fullSize = glGetUniformLocation(prog, "fullSize"); rectShader.radius = glGetUniformLocation(prog, "radius"); prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA); texShader.program = prog; texShader.proj = glGetUniformLocation(prog, "proj"); texShader.tex = glGetUniformLocation(prog, "tex"); texShader.alphaMatte = glGetUniformLocation(prog, "texMatte"); texShader.alpha = glGetUniformLocation(prog, "alpha"); texShader.texAttrib = glGetAttribLocation(prog, "texcoord"); texShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); texShader.posAttrib = glGetAttribLocation(prog, "pos"); texShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); texShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); texShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); texShader.topLeft = glGetUniformLocation(prog, "topLeft"); texShader.fullSize = glGetUniformLocation(prog, "fullSize"); texShader.radius = glGetUniformLocation(prog, "radius"); texShader.applyTint = glGetUniformLocation(prog, "applyTint"); texShader.tint = glGetUniformLocation(prog, "tint"); texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); prog = createProgram(TEXVERTSRC, TEXMIXFRAGSRCRGBA); texMixShader.program = prog; texMixShader.proj = glGetUniformLocation(prog, "proj"); texMixShader.tex = glGetUniformLocation(prog, "tex1"); texMixShader.tex2 = glGetUniformLocation(prog, "tex2"); texMixShader.alphaMatte = glGetUniformLocation(prog, "texMatte"); texMixShader.alpha = glGetUniformLocation(prog, "alpha"); texMixShader.mixFactor = glGetUniformLocation(prog, "mixFactor"); texMixShader.texAttrib = glGetAttribLocation(prog, "texcoord"); texMixShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); texMixShader.posAttrib = glGetAttribLocation(prog, "pos"); texMixShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); texMixShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); texMixShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); texMixShader.topLeft = glGetUniformLocation(prog, "topLeft"); texMixShader.fullSize = glGetUniformLocation(prog, "fullSize"); texMixShader.radius = glGetUniformLocation(prog, "radius"); texMixShader.applyTint = glGetUniformLocation(prog, "applyTint"); texMixShader.tint = glGetUniformLocation(prog, "tint"); texMixShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); prog = createProgram(TEXVERTSRC, FRAGBLUR1); blurShader1.program = prog; blurShader1.tex = glGetUniformLocation(prog, "tex"); blurShader1.alpha = glGetUniformLocation(prog, "alpha"); blurShader1.proj = glGetUniformLocation(prog, "proj"); blurShader1.posAttrib = glGetAttribLocation(prog, "pos"); blurShader1.texAttrib = glGetAttribLocation(prog, "texcoord"); blurShader1.radius = glGetUniformLocation(prog, "radius"); blurShader1.halfpixel = glGetUniformLocation(prog, "halfpixel"); blurShader1.passes = glGetUniformLocation(prog, "passes"); blurShader1.vibrancy = glGetUniformLocation(prog, "vibrancy"); blurShader1.vibrancy_darkness = glGetUniformLocation(prog, "vibrancy_darkness"); prog = createProgram(TEXVERTSRC, FRAGBLUR2); blurShader2.program = prog; blurShader2.tex = glGetUniformLocation(prog, "tex"); blurShader2.alpha = glGetUniformLocation(prog, "alpha"); blurShader2.proj = glGetUniformLocation(prog, "proj"); blurShader2.posAttrib = glGetAttribLocation(prog, "pos"); blurShader2.texAttrib = glGetAttribLocation(prog, "texcoord"); blurShader2.radius = glGetUniformLocation(prog, "radius"); blurShader2.halfpixel = glGetUniformLocation(prog, "halfpixel"); prog = createProgram(TEXVERTSRC, FRAGBLURPREPARE); blurPrepareShader.program = prog; blurPrepareShader.tex = glGetUniformLocation(prog, "tex"); blurPrepareShader.proj = glGetUniformLocation(prog, "proj"); blurPrepareShader.posAttrib = glGetAttribLocation(prog, "pos"); blurPrepareShader.texAttrib = glGetAttribLocation(prog, "texcoord"); blurPrepareShader.contrast = glGetUniformLocation(prog, "contrast"); blurPrepareShader.brightness = glGetUniformLocation(prog, "brightness"); prog = createProgram(TEXVERTSRC, FRAGBLURFINISH); blurFinishShader.program = prog; blurFinishShader.tex = glGetUniformLocation(prog, "tex"); blurFinishShader.proj = glGetUniformLocation(prog, "proj"); blurFinishShader.posAttrib = glGetAttribLocation(prog, "pos"); blurFinishShader.texAttrib = glGetAttribLocation(prog, "texcoord"); blurFinishShader.brightness = glGetUniformLocation(prog, "brightness"); blurFinishShader.noise = glGetUniformLocation(prog, "noise"); blurFinishShader.colorize = glGetUniformLocation(prog, "colorize"); blurFinishShader.colorizeTint = glGetUniformLocation(prog, "colorizeTint"); blurFinishShader.boostA = glGetUniformLocation(prog, "boostA"); prog = createProgram(QUADVERTSRC, FRAGBORDER); borderShader.program = prog; borderShader.proj = glGetUniformLocation(prog, "proj"); borderShader.thick = glGetUniformLocation(prog, "thick"); borderShader.posAttrib = glGetAttribLocation(prog, "pos"); borderShader.texAttrib = glGetAttribLocation(prog, "texcoord"); borderShader.topLeft = glGetUniformLocation(prog, "topLeft"); borderShader.bottomRight = glGetUniformLocation(prog, "bottomRight"); borderShader.fullSize = glGetUniformLocation(prog, "fullSize"); borderShader.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed"); borderShader.radius = glGetUniformLocation(prog, "radius"); borderShader.radiusOuter = glGetUniformLocation(prog, "radiusOuter"); borderShader.gradient = glGetUniformLocation(prog, "gradient"); borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength"); borderShader.angle = glGetUniformLocation(prog, "angle"); borderShader.gradient2 = glGetUniformLocation(prog, "gradient2"); borderShader.gradient2Length = glGetUniformLocation(prog, "gradient2Length"); borderShader.angle2 = glGetUniformLocation(prog, "angle2"); borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp"); borderShader.alpha = glGetUniformLocation(prog, "alpha"); g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); } // CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) { projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL); g_pEGL->makeCurrent(surf.eglSurface); glViewport(0, 0, surf.size.x, surf.size.y); GLint fb = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb); pushFb(fb); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); SRenderFeedback feedback; const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !g_pAsyncResourceGatherer->gathered; if (!WAITFORASSETS) { // render widgets const auto WIDGETS = getOrCreateWidgetsFor(surf); for (auto& w : WIDGETS) { feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame; } } feedback.needsFrame = feedback.needsFrame || !g_pAsyncResourceGatherer->gathered; glDisable(GL_BLEND); return feedback; } void CRenderer::renderRect(const CBox& box, const CHyprColor& col, int rounding) { const auto ROUNDEDBOX = box.copy().round(); Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot); Mat3x3 glMatrix = projection.copy().multiply(matrix); glUseProgram(rectShader.program); glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); // premultiply the color as well as we don't work with straight alpha glUniform4f(rectShader.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a); const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); // Rounded corners glUniform2f(rectShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); glUniform2f(rectShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform1f(rectShader.radius, rounding); glVertexAttribPointer(rectShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(rectShader.posAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(rectShader.posAttrib); } void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding, float alpha) { const auto ROUNDEDBOX = box.copy().round(); Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot); Mat3x3 glMatrix = projection.copy().multiply(matrix); glUseProgram(borderShader.program); glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform4fv(borderShader.gradient, gradient.m_vColorsOkLabA.size() / 4, (float*)gradient.m_vColorsOkLabA.data()); glUniform1i(borderShader.gradientLength, gradient.m_vColorsOkLabA.size() / 4); glUniform1f(borderShader.angle, (int)(gradient.m_fAngle / (M_PI / 180.0)) % 360 * (M_PI / 180.0)); glUniform1f(borderShader.alpha, alpha); glUniform1i(borderShader.gradient2Length, 0); const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); glUniform2f(borderShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); glUniform2f(borderShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform2f(borderShader.fullSizeUntransformed, (float)box.width, (float)box.height); glUniform1f(borderShader.radius, rounding); glUniform1f(borderShader.radiusOuter, rounding); glUniform1f(borderShader.thick, thickness); glVertexAttribPointer(borderShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(borderShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(borderShader.posAttrib); glEnableVertexAttribArray(borderShader.texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(borderShader.posAttrib); glDisableVertexAttribArray(borderShader.texAttrib); } void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional tr) { const auto ROUNDEDBOX = box.copy().round(); Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot); Mat3x3 glMatrix = projection.copy().multiply(matrix); CShader* shader = &texShader; glActiveTexture(GL_TEXTURE0); glBindTexture(tex.m_iTarget, tex.m_iTexID); glUseProgram(shader->program); glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1i(shader->tex, 0); glUniform1f(shader->alpha, a); const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); // Rounded corners glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); glUniform1f(shader->radius, rounding); glUniform1i(shader->discardOpaque, 0); glUniform1i(shader->discardAlpha, 0); glUniform1i(shader->applyTint, 0); glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(shader->posAttrib); glEnableVertexAttribArray(shader->texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(shader->posAttrib); glDisableVertexAttribArray(shader->texAttrib); glBindTexture(tex.m_iTarget, 0); } void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a, float mixFactor, int rounding, std::optional tr) { const auto ROUNDEDBOX = box.copy().round(); Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot); Mat3x3 glMatrix = projection.copy().multiply(matrix); CShader* shader = &texMixShader; glActiveTexture(GL_TEXTURE0); glBindTexture(tex.m_iTarget, tex.m_iTexID); glActiveTexture(GL_TEXTURE1); glBindTexture(tex2.m_iTarget, tex2.m_iTexID); glUseProgram(shader->program); glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1i(shader->tex, 0); glUniform1i(shader->tex2, 1); glUniform1f(shader->alpha, a); glUniform1f(shader->mixFactor, mixFactor); const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); // Rounded corners glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); glUniform1f(shader->radius, rounding); glUniform1i(shader->discardOpaque, 0); glUniform1i(shader->discardAlpha, 0); glUniform1i(shader->applyTint, 0); glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(shader->posAttrib); glEnableVertexAttribArray(shader->texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(shader->posAttrib); glDisableVertexAttribArray(shader->texAttrib); glBindTexture(tex.m_iTarget, 0); } template static void createWidget(std::vector>& widgets) { const auto W = makeAtomicShared(); W->registerSelf(W); widgets.emplace_back(W); } std::vector>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface& surf) { RASSERT(surf.m_outputID != OUTPUT_INVALID, "Invalid output ID!"); if (!widgets.contains(surf.m_outputID)) { auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); std::ranges::sort(CWIDGETS, [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) { return std::any_cast(a.values.at("zindex")) < std::any_cast(b.values.at("zindex")); }); const auto POUTPUT = surf.m_outputRef.lock(); for (auto& c : CWIDGETS) { if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !("desc:" + POUTPUT->stringDesc).starts_with(c.monitor)) continue; // by type if (c.type == "background") { createWidget(widgets[surf.m_outputID]); } else if (c.type == "input-field") { createWidget(widgets[surf.m_outputID]); } else if (c.type == "label") { createWidget(widgets[surf.m_outputID]); } else if (c.type == "shape") { createWidget(widgets[surf.m_outputID]); } else if (c.type == "image") { createWidget(widgets[surf.m_outputID]); } else { Debug::log(ERR, "Unknown widget type: {}", c.type); continue; } widgets[surf.m_outputID].back()->configure(c.values, POUTPUT); } } return widgets[surf.m_outputID]; } void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) { glDisable(GL_BLEND); glDisable(GL_STENCIL_TEST); CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y}; box.round(); Mat3x3 matrix = projMatrix.projectBox(box, HYPRUTILS_TRANSFORM_NORMAL, 0); Mat3x3 glMatrix = projection.copy().multiply(matrix); CFramebuffer mirrors[2]; mirrors[0].alloc(outfb.m_vSize.x, outfb.m_vSize.y, true); mirrors[1].alloc(outfb.m_vSize.x, outfb.m_vSize.y, true); CFramebuffer* currentRenderToFB = &mirrors[0]; // Begin with base color adjustments - global brightness and contrast // TODO: make this a part of the first pass maybe to save on a drawcall? { mirrors[1].bind(); glActiveTexture(GL_TEXTURE0); glBindTexture(outfb.m_cTex.m_iTarget, outfb.m_cTex.m_iTexID); glTexParameteri(outfb.m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(blurPrepareShader.program); glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1f(blurPrepareShader.contrast, params.contrast); glUniform1f(blurPrepareShader.brightness, params.brightness); glUniform1i(blurPrepareShader.tex, 0); glVertexAttribPointer(blurPrepareShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(blurPrepareShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(blurPrepareShader.posAttrib); glEnableVertexAttribArray(blurPrepareShader.texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(blurPrepareShader.posAttrib); glDisableVertexAttribArray(blurPrepareShader.texAttrib); currentRenderToFB = &mirrors[1]; } // declare the draw func auto drawPass = [&](CShader* pShader) { if (currentRenderToFB == &mirrors[0]) mirrors[1].bind(); else mirrors[0].bind(); glActiveTexture(GL_TEXTURE0); glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID); glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(pShader->program); // prep two shaders glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1f(pShader->radius, params.size); if (pShader == &blurShader1) { glUniform2f(blurShader1.halfpixel, 0.5f / (outfb.m_vSize.x / 2.f), 0.5f / (outfb.m_vSize.y / 2.f)); glUniform1i(blurShader1.passes, params.passes); glUniform1f(blurShader1.vibrancy, params.vibrancy); glUniform1f(blurShader1.vibrancy_darkness, params.vibrancy_darkness); } else glUniform2f(blurShader2.halfpixel, 0.5f / (outfb.m_vSize.x * 2.f), 0.5f / (outfb.m_vSize.y * 2.f)); glUniform1i(pShader->tex, 0); glVertexAttribPointer(pShader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(pShader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(pShader->posAttrib); glEnableVertexAttribArray(pShader->texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(pShader->posAttrib); glDisableVertexAttribArray(pShader->texAttrib); if (currentRenderToFB != &mirrors[0]) currentRenderToFB = &mirrors[0]; else currentRenderToFB = &mirrors[1]; }; // draw the things. // first draw is swap -> mirr mirrors[0].bind(); glBindTexture(mirrors[1].m_cTex.m_iTarget, mirrors[1].m_cTex.m_iTexID); for (int i = 1; i <= params.passes; ++i) { drawPass(&blurShader1); // down } for (int i = params.passes - 1; i >= 0; --i) { drawPass(&blurShader2); // up } // finalize the image { if (currentRenderToFB == &mirrors[0]) mirrors[1].bind(); else mirrors[0].bind(); glActiveTexture(GL_TEXTURE0); glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID); glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(blurFinishShader.program); glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); glUniform1f(blurFinishShader.noise, params.noise); glUniform1f(blurFinishShader.brightness, params.brightness); glUniform1i(blurFinishShader.colorize, params.colorize.has_value()); if (params.colorize.has_value()) glUniform3f(blurFinishShader.colorizeTint, params.colorize->r, params.colorize->g, params.colorize->b); glUniform1f(blurFinishShader.boostA, params.boostA); glUniform1i(blurFinishShader.tex, 0); glVertexAttribPointer(blurFinishShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(blurFinishShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glEnableVertexAttribArray(blurFinishShader.posAttrib); glEnableVertexAttribArray(blurFinishShader.texAttrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(blurFinishShader.posAttrib); glDisableVertexAttribArray(blurFinishShader.texAttrib); if (currentRenderToFB != &mirrors[0]) currentRenderToFB = &mirrors[0]; else currentRenderToFB = &mirrors[1]; } // finish outfb.bind(); renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, HYPRUTILS_TRANSFORM_NORMAL); glEnable(GL_BLEND); } void CRenderer::pushFb(GLint fb) { boundFBs.push_back(fb); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb); } void CRenderer::popFb() { boundFBs.pop_back(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundFBs.empty() ? 0 : boundFBs.back()); } void CRenderer::removeWidgetsFor(OUTPUTID id) { widgets.erase(id); } void CRenderer::reconfigureWidgetsFor(OUTPUTID id) { // TODO: reconfigure widgets by just calling their configure method again. // Requires a way to get a widgets config properties. // I think the best way would be to store the anonymos key of the widget config. removeWidgetsFor(id); } void CRenderer::startFadeIn() { Debug::log(LOG, "Starting fade in"); *opacity = 1.f; opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true); } void CRenderer::startFadeOut(bool unlock) { *opacity = 0.f; if (unlock) opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true); } void CRenderer::warpOpacity(float newOpacity) { opacity->setValueAndWarp(newOpacity); } hyprlock-0.9.2/src/renderer/Renderer.hpp000066400000000000000000000051771506744673100202600ustar00rootroot00000000000000#pragma once #include #include #include "Shader.hpp" #include "../defines.hpp" #include "../core/LockSurface.hpp" #include "../helpers/AnimatedVariable.hpp" #include "../helpers/Color.hpp" #include "AsyncResourceGatherer.hpp" #include "../config/ConfigDataValues.hpp" #include "widgets/IWidget.hpp" #include "Framebuffer.hpp" typedef std::unordered_map>> widgetMap_t; class CRenderer { public: CRenderer(); struct SRenderFeedback { bool needsFrame = false; }; struct SBlurParams { int size = 0, passes = 0; float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0; std::optional colorize; float boostA = 1.0; }; SRenderFeedback renderLock(const CSessionLockSurface& surf); void renderRect(const CBox& box, const CHyprColor& col, int rounding = 0); void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); void blurFB(const CFramebuffer& outfb, SBlurParams params); std::chrono::system_clock::time_point firstFullFrameTime; void pushFb(GLint fb); void popFb(); void removeWidgetsFor(OUTPUTID id); void reconfigureWidgetsFor(OUTPUTID id); void startFadeIn(); void startFadeOut(bool unlock = false); void warpOpacity(float warpOpacity); std::vector>& getOrCreateWidgetsFor(const CSessionLockSurface& surf); private: widgetMap_t widgets; CShader rectShader; CShader texShader; CShader texMixShader; CShader blurShader1; CShader blurShader2; CShader blurPrepareShader; CShader blurFinishShader; CShader borderShader; Mat3x3 projMatrix = Mat3x3::identity(); Mat3x3 projection; PHLANIMVAR opacity; std::vector boundFBs; }; inline UP g_pRenderer; hyprlock-0.9.2/src/renderer/Screencopy.cpp000066400000000000000000000450641506744673100206160ustar00rootroot00000000000000#include "Screencopy.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include "../core/hyprlock.hpp" #include "../core/Egl.hpp" #include "../config/ConfigManager.hpp" #include "wlr-screencopy-unstable-v1.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; // std::string CScreencopyFrame::getResourceId(SP pOutput) { return RESOURCEIDPREFIX + std::format(":{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y); } CScreencopyFrame::CScreencopyFrame(SP pOutput) : m_outputRef(pOutput) { m_asset = makeAtomicShared(); captureOutput(); static const auto SCMODE = g_pConfigManager->getValue("general:screencopy_mode"); if (*SCMODE == 1) m_frame = makeUnique(m_sc); else m_frame = makeUnique(m_sc); } void CScreencopyFrame::captureOutput() { const auto POUTPUT = m_outputRef.lock(); RASSERT(POUTPUT, "Screencopy, but no valid output"); m_resourceID = getResourceId(POUTPUT); m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, POUTPUT->m_wlOutput->resource())); m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) { Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this); if (!m_frame || !m_frame->onBufferDone() || !m_frame->m_wlBuffer) { Debug::log(ERR, "[sc] Failed to create a wayland buffer for the screencopy frame"); return; } m_sc->sendCopy(m_frame->m_wlBuffer->resource()); Debug::log(TRACE, "[sc] wlr frame copied"); }); m_sc->setFailed([this](CCZwlrScreencopyFrameV1* r) { Debug::log(ERR, "[sc] wlrOnFailed for {}", (void*)r); m_frame.reset(); }); m_sc->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this); if (!m_frame || !m_frame->onBufferReady(m_asset)) { Debug::log(ERR, "[sc] Failed to bind the screencopy buffer to a texture"); return; } m_sc.reset(); }); } CSCDMAFrame::CSCDMAFrame(SP sc) : m_sc(sc) { if (!glEGLImageTargetTexture2DOES) { glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (!glEGLImageTargetTexture2DOES) { Debug::log(ERR, "[sc] No glEGLImageTargetTexture2DOES??"); return; } } if (!g_pHyprlock->dma.linuxDmabuf) { Debug::log(ERR, "[sc] No DMABUF support?"); return; } if (!g_pHyprlock->dma.gbmDevice) { Debug::log(ERR, "[sc] No gbmDevice for DMABUF was created?"); return; } if (!eglQueryDmaBufModifiersEXT) eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); m_sc->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) { Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this); m_w = width; m_h = height; m_fmt = format; Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format); }); m_sc->setBuffer([](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { ; // unused by dma }); } CSCDMAFrame::~CSCDMAFrame() { if (g_pEGL) eglDestroyImage(g_pEGL->eglDisplay, m_image); // leaks bo and stuff but lives throughout so for now who cares } bool CSCDMAFrame::onBufferDone() { uint32_t flags = GBM_BO_USE_RENDERING; if (!eglQueryDmaBufModifiersEXT) { Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support"); m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags); } else { std::array mods; std::array externalOnly; int num = 0; if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, m_fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) { Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo"); m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags); } else { Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num); std::vector goodMods; for (int i = 0; i < num; ++i) { if (externalOnly[i]) { Debug::log(TRACE, "Modifier {:x} failed test", mods[i]); continue; } Debug::log(TRACE, "Modifier {:x} passed test", mods[i]); goodMods.emplace_back(mods[i]); } m_bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, goodMods.data(), goodMods.size(), flags); } } if (!m_bo) { Debug::log(ERR, "[bo] Couldn't create a drm buffer"); return false; } m_planes = gbm_bo_get_plane_count(m_bo); Debug::log(LOG, "[bo] has {} plane(s)", m_planes); m_mod = gbm_bo_get_modifier(m_bo); Debug::log(LOG, "[bo] chose modifier {:x}", m_mod); auto params = makeShared(g_pHyprlock->dma.linuxDmabuf->sendCreateParams()); if (!params) { Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed"); gbm_bo_destroy(m_bo); return false; } for (size_t plane = 0; plane < (size_t)m_planes; plane++) { m_stride[plane] = gbm_bo_get_stride_for_plane(m_bo, plane); m_offset[plane] = gbm_bo_get_offset(m_bo, plane); m_fd[plane] = gbm_bo_get_fd_for_plane(m_bo, plane); if (m_fd[plane] < 0) { Debug::log(ERR, "gbm_m_bo_get_fd_for_plane failed"); params.reset(); gbm_bo_destroy(m_bo); for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { close(m_fd[plane_tmp]); } return false; } params->sendAdd(m_fd[plane], plane, m_offset[plane], m_stride[plane], m_mod >> 32, m_mod & 0xffffffff); } m_wlBuffer = makeShared(params->sendCreateImmed(m_w, m_h, m_fmt, (zwpLinuxBufferParamsV1Flags)0)); params.reset(); if (!m_wlBuffer) { Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); gbm_bo_destroy(m_bo); for (size_t plane = 0; plane < (size_t)m_planes; plane++) close(m_fd[plane]); return false; } return true; } bool CSCDMAFrame::onBufferReady(ASP asset) { static constexpr struct { EGLAttrib fd; EGLAttrib offset; EGLAttrib pitch; EGLAttrib modlo; EGLAttrib modhi; } attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT, .offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE1_FD_EXT, .offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE2_FD_EXT, .offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE3_FD_EXT, .offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; std::vector attribs = { EGL_WIDTH, m_w, EGL_HEIGHT, m_h, EGL_LINUX_DRM_FOURCC_EXT, m_fmt, }; for (int i = 0; i < m_planes; i++) { attribs.emplace_back(attrNames[i].fd); attribs.emplace_back(m_fd[i]); attribs.emplace_back(attrNames[i].offset); attribs.emplace_back(m_offset[i]); attribs.emplace_back(attrNames[i].pitch); attribs.emplace_back(m_stride[i]); if (m_mod != DRM_FORMAT_MOD_INVALID) { attribs.emplace_back(attrNames[i].modlo); attribs.emplace_back(m_mod & 0xFFFFFFFF); attribs.emplace_back(attrNames[i].modhi); attribs.emplace_back(m_mod >> 32); } } attribs.emplace_back(EGL_NONE); m_image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data()); if (m_image == EGL_NO_IMAGE) { Debug::log(ERR, "Failed creating an egl image"); return false; } asset->texture.allocate(); asset->texture.m_vSize = {m_w, m_h}; glBindTexture(GL_TEXTURE_2D, asset->texture.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); glBindTexture(GL_TEXTURE_2D, 0); Debug::log(LOG, "Got dma frame with size {}", asset->texture.m_vSize); asset->ready = true; return true; } CSCSHMFrame::CSCSHMFrame(SP sc) : m_sc(sc) { Debug::log(TRACE, "[sc] [shm] Creating a SHM frame"); m_sc->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { Debug::log(TRACE, "[sc] [shm] wlrOnBuffer for {}", (void*)this); const auto SIZE = stride * height; m_shmFmt = format; m_w = width; m_h = height; m_stride = stride; // Create a shm pool with format and size std::string shmPoolFile; const auto FD = createPoolFile(SIZE, shmPoolFile); if (FD < 0) { Debug::log(ERR, "[sc] [shm] failed to create a pool file"); return; } m_shmData = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0); if (m_shmData == MAP_FAILED) { Debug::log(ERR, "[sc] [shm] failed to (errno {})", strerror(errno)); close(FD); m_ok = false; return; } if (!g_pHyprlock->getShm()) { Debug::log(ERR, "[sc] [shm] Failed to get WLShm global"); close(FD); m_ok = false; return; } auto pShmPool = makeShared(g_pHyprlock->getShm()->sendCreatePool(FD, SIZE)); m_wlBuffer = makeShared(pShmPool->sendCreateBuffer(0, width, height, stride, m_shmFmt)); pShmPool.reset(); close(FD); }); m_sc->setLinuxDmabuf([](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { ; // unused by scshm }); } CSCSHMFrame::~CSCSHMFrame() { if (m_convBuffer) free(m_convBuffer); if (m_shmData) munmap(m_shmData, m_stride * m_h); } void CSCSHMFrame::convertBuffer() { const auto BYTESPERPX = m_stride / m_w; if (BYTESPERPX == 4) { switch (m_shmFmt) { case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_XRGB8888: { Debug::log(LOG, "[sc] [shm] Converting ARGB to RGBA"); uint8_t* data = (uint8_t*)m_shmData; for (uint32_t y = 0; y < m_h; ++y) { for (uint32_t x = 0; x < m_w; ++x) { struct pixel { // little-endian ARGB unsigned char blue; unsigned char green; unsigned char red; unsigned char alpha; }* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4)); // RGBA *px = {.blue = px->red, .green = px->green, .red = px->blue, .alpha = px->alpha}; } } } break; case WL_SHM_FORMAT_ABGR8888: case WL_SHM_FORMAT_XBGR8888: { Debug::log(LOG, "[sc] [shm] Converting ABGR to RGBA"); uint8_t* data = (uint8_t*)m_shmData; for (uint32_t y = 0; y < m_h; ++y) { for (uint32_t x = 0; x < m_w; ++x) { struct pixel { // little-endian ARGB unsigned char blue; unsigned char green; unsigned char red; unsigned char alpha; }* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4)); // BGRA *px = {.blue = px->blue, .green = px->green, .red = px->red, .alpha = px->alpha}; } } } break; case WL_SHM_FORMAT_ABGR2101010: case WL_SHM_FORMAT_ARGB2101010: case WL_SHM_FORMAT_XRGB2101010: case WL_SHM_FORMAT_XBGR2101010: { Debug::log(LOG, "[sc] [shm] Converting 10-bit channels to 8-bit"); uint8_t* data = (uint8_t*)m_shmData; const bool FLIP = m_shmFmt != WL_SHM_FORMAT_XBGR2101010; for (uint32_t y = 0; y < m_h; ++y) { for (uint32_t x = 0; x < m_w; ++x) { uint32_t* px = (uint32_t*)(data + (y * m_w * 4) + (x * 4)); // conv to 8 bit uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0)); uint8_t G = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000011111111110000000000) >> 10) / 1023.0)); uint8_t B = (uint8_t)std::round((255.0 * (((*px) & 0b00111111111100000000000000000000) >> 20) / 1023.0)); uint8_t A = (uint8_t)std::round((255.0 * (((*px) & 0b11000000000000000000000000000000) >> 30) / 3.0)); // write 8-bit values *px = ((FLIP ? B : R) << 0) + (G << 8) + ((FLIP ? R : B) << 16) + (A << 24); } } } break; default: { Debug::log(WARN, "[sc] [shm] Unsupported format {}", m_shmFmt); } } } else if (BYTESPERPX == 3) { Debug::log(LOG, "[sc] [shm] Converting 24 bit to 32 bit"); m_convBuffer = malloc(m_w * m_h * 4); const int NEWSTRIDE = m_w * 4; RASSERT(m_convBuffer, "malloc failed"); switch (m_shmFmt) { case WL_SHM_FORMAT_BGR888: { Debug::log(LOG, "[sc] [shm] Converting BGR to RGBA"); for (uint32_t y = 0; y < m_h; ++y) { for (uint32_t x = 0; x < m_w; ++x) { struct pixel3 { // little-endian RGB unsigned char blue; unsigned char green; unsigned char red; }* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3)); struct pixel4 { // little-endian ARGB unsigned char blue; unsigned char green; unsigned char red; unsigned char alpha; }* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4)); *dstPx = {.blue = srcPx->blue, .green = srcPx->green, .red = srcPx->red, .alpha = 0xFF}; } } } break; case WL_SHM_FORMAT_RGB888: { Debug::log(LOG, "[sc] [shm] Converting RGB to RGBA"); for (uint32_t y = 0; y < m_h; ++y) { for (uint32_t x = 0; x < m_w; ++x) { struct pixel3 { // big-endian RGB unsigned char red; unsigned char green; unsigned char blue; }* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3)); struct pixel4 { // big-endian ARGB unsigned char alpha; unsigned char red; unsigned char green; unsigned char blue; }* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4)); *dstPx = {.alpha = srcPx->red, .red = srcPx->green, .green = srcPx->blue, .blue = 0xFF}; } } } break; default: { Debug::log(ERR, "[sc] [shm] Unsupported format for 24bit buffer {}", m_shmFmt); } } } else { Debug::log(ERR, "[sc] [shm] Unsupported bytes per pixel {}", BYTESPERPX); } } bool CSCSHMFrame::onBufferReady(ASP asset) { convertBuffer(); asset->texture.allocate(); asset->texture.m_vSize.x = m_w; asset->texture.m_vSize.y = m_h; glBindTexture(GL_TEXTURE_2D, asset->texture.m_iTexID); void* buffer = m_convBuffer ? m_convBuffer : m_shmData; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glBindTexture(GL_TEXTURE_2D, 0); Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset->texture.m_vSize); asset->ready = true; return true; } hyprlock-0.9.2/src/renderer/Screencopy.hpp000066400000000000000000000047361506744673100206240ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "../core/Output.hpp" #include #include #include #include "Shared.hpp" #include "linux-dmabuf-v1.hpp" #include "wlr-screencopy-unstable-v1.hpp" class ISCFrame { public: ISCFrame() = default; virtual ~ISCFrame() = default; virtual bool onBufferDone() = 0; virtual bool onBufferReady(ASP asset) = 0; SP m_wlBuffer = nullptr; }; class CScreencopyFrame { public: static std::string getResourceId(SP pOutput); static constexpr const std::string RESOURCEIDPREFIX = "screencopy"; CScreencopyFrame(SP pOutput); ~CScreencopyFrame() = default; void captureOutput(); SP m_sc = nullptr; std::string m_resourceID; ASP m_asset; private: WP m_outputRef; UP m_frame = nullptr; bool m_dmaFailed = false; }; // Uses a gpu buffer created via gbm_bo class CSCDMAFrame : public ISCFrame { public: CSCDMAFrame(SP sc); virtual ~CSCDMAFrame(); virtual bool onBufferReady(ASP asset); virtual bool onBufferDone(); private: gbm_bo* m_bo = nullptr; int m_planes = 0; uint64_t m_mod = 0; int m_fd[4]; uint32_t m_stride[4], m_offset[4]; int m_w = 0, m_h = 0; uint32_t m_fmt = 0; SP m_sc = nullptr; EGLImage m_image = nullptr; }; // Uses a shm buffer - is slow and needs ugly format conversion // Used as a fallback just in case. class CSCSHMFrame : public ISCFrame { public: CSCSHMFrame(SP sc); virtual ~CSCSHMFrame(); virtual bool onBufferDone() { return m_ok; } virtual bool onBufferReady(ASP asset); void convertBuffer(); private: bool m_ok = true; uint32_t m_w = 0, m_h = 0; uint32_t m_stride = 0; SP m_sc = nullptr; uint32_t m_shmFmt = 0; void* m_shmData = nullptr; void* m_convBuffer = nullptr; }; hyprlock-0.9.2/src/renderer/Shader.cpp000066400000000000000000000007131506744673100177020ustar00rootroot00000000000000#include "Shader.hpp" GLint CShader::getUniformLocation(const std::string& unif) { const auto itpos = m_muUniforms.find(unif); if (itpos == m_muUniforms.end()) { const auto unifLoc = glGetUniformLocation(program, unif.c_str()); m_muUniforms[unif] = unifLoc; return unifLoc; } return itpos->second; } CShader::~CShader() { destroy(); } void CShader::destroy() { glDeleteProgram(program); program = 0; }hyprlock-0.9.2/src/renderer/Shader.hpp000066400000000000000000000036051506744673100177120ustar00rootroot00000000000000#pragma once #include #include #include class CShader { public: ~CShader(); GLuint program = 0; GLint proj = -1; GLint color = -1; GLint alphaMatte = -1; GLint tex = -1; GLint tex2 = -1; GLint alpha = -1; GLfloat mixFactor = -1; GLint posAttrib = -1; GLint texAttrib = -1; GLint matteTexAttrib = -1; GLint discardOpaque = -1; GLint discardAlpha = -1; GLfloat discardAlphaValue = -1; GLint topLeft = -1; GLint bottomRight = -1; GLint fullSize = -1; GLint fullSizeUntransformed = -1; GLint radius = -1; GLint radiusOuter = -1; GLint thick = -1; GLint halfpixel = -1; GLint range = -1; GLint shadowPower = -1; GLint useAlphaMatte = -1; // always inverted GLint applyTint = -1; GLint tint = -1; GLint gradient = -1; GLint gradientLength = -1; GLint gradient2 = -1; GLint gradient2Length = -1; GLint gradientLerp = -1; GLint angle = -1; GLint angle2 = -1; GLint time = -1; GLint distort = -1; GLint wl_output = -1; // Blur prepare GLint contrast = -1; // Blur GLint passes = -1; // Used by `vibrancy` GLint vibrancy = -1; GLint vibrancy_darkness = -1; // Blur finish GLint brightness = -1; GLint noise = -1; // colorize GLint colorize = -1; GLint colorizeTint = -1; GLint boostA = -1; GLint getUniformLocation(const std::string&); void destroy(); private: std::unordered_map m_muUniforms; };hyprlock-0.9.2/src/renderer/Shaders.hpp000066400000000000000000000405621506744673100201000ustar00rootroot00000000000000#pragma once #include #include #include constexpr float SHADER_ROUNDED_SMOOTHING_FACTOR = M_PI / 5.34665792551; inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVarName) -> std::string { return R"#( // branchless baby! highp vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; pixCoord -= fullSize * 0.5 - radius; pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left // smoothing constant for the edge: more = blurrier, but smoother const float SMOOTHING_CONSTANT = )#" + std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(; if (pixCoord.x + pixCoord.y > radius) { float dist = length(pixCoord); if (dist > radius + SMOOTHING_CONSTANT * 2.0) discard; if (dist > radius - SMOOTHING_CONSTANT * 2.0) { float dist = length(pixCoord); float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); )#" + colorVarName + R"#( = )#" + colorVarName + R"#( * normalized; } } )#"; }; inline const std::string QUADVERTSRC = R"#( uniform mat3 proj; uniform vec4 color; attribute vec2 pos; attribute vec2 texcoord; attribute vec2 texcoordMatte; varying vec4 v_color; varying vec2 v_texcoord; varying vec2 v_texcoordMatte; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_color = color; v_texcoord = texcoord; v_texcoordMatte = texcoordMatte; })#"; inline const std::string QUADFRAGSRC = R"#( precision highp float; varying vec4 v_color; uniform vec2 topLeft; uniform vec2 fullSize; uniform float radius; void main() { vec4 pixColor = v_color; if (radius > 0.0) { )#" + ROUNDED_SHADER_FUNC("pixColor") + R"#( } gl_FragColor = pixColor; })#"; inline const std::string TEXVERTSRC = 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 TEXFRAGSRCRGBA = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float alpha; uniform vec2 topLeft; uniform vec2 fullSize; uniform float radius; uniform int discardOpaque; uniform int discardAlpha; uniform float discardAlphaValue; uniform int applyTint; uniform vec3 tint; void main() { vec4 pixColor = texture2D(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) discard; if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) discard; if (applyTint == 1) { pixColor[0] = pixColor[0] * tint[0]; pixColor[1] = pixColor[1] * tint[1]; pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) { )#" + ROUNDED_SHADER_FUNC("pixColor") + R"#( } gl_FragColor = pixColor * alpha; })#"; inline const std::string TEXMIXFRAGSRCRGBA = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex1; uniform sampler2D tex2; uniform float mixFactor; uniform float alpha; uniform vec2 topLeft; uniform vec2 fullSize; uniform float radius; uniform int discardOpaque; uniform int discardAlpha; uniform float discardAlphaValue; uniform int applyTint; uniform vec3 tint; void main() { vec4 pixColor = mix(texture2D(tex1, v_texcoord), texture2D(tex2, v_texcoord), smoothstep(0.0, 1.0, mixFactor)); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) discard; if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) discard; if (applyTint == 1) { pixColor[0] = pixColor[0] * tint[0]; pixColor[1] = pixColor[1] * tint[1]; pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) { )#" + ROUNDED_SHADER_FUNC("pixColor") + R"#( } gl_FragColor = pixColor * alpha; })#"; inline const std::string FRAGBLUR1 = R"#( #version 100 precision highp float; varying highp vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float radius; uniform vec2 halfpixel; uniform int passes; uniform float vibrancy; uniform float vibrancy_darkness; // see http://alienryderflex.com/hsp.html const float Pr = 0.299; const float Pg = 0.587; const float Pb = 0.114; // Y is "v" ( brightness ). X is "s" ( saturation ) // see https://www.desmos.com/3d/a88652b9a4 // Determines if high brightness or high saturation is more important const float a = 0.93; const float b = 0.11; const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors // // http://www.flong.com/archive/texts/code/shapers_circ/ float doubleCircleSigmoid(float x, float a) { a = clamp(a, 0.0, 1.0); float y = .0; if (x <= a) { y = a - sqrt(a * a - x * x); } else { y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); } return y; } vec3 rgb2hsl(vec3 col) { float red = col.r; float green = col.g; float blue = col.b; float minc = min(col.r, min(col.g, col.b)); float maxc = max(col.r, max(col.g, col.b)); float delta = maxc - minc; float lum = (minc + maxc) * 0.5; float sat = 0.0; float hue = 0.0; if (lum > 0.0 && lum < 1.0) { float mul = (lum < 0.5) ? (lum) : (1.0 - lum); sat = delta / (mul * 2.0); } if (delta > 0.0) { vec3 maxcVec = vec3(maxc); vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; hue += dot(adds, masks); hue /= 6.0; if (hue < 0.0) hue += 1.0; } return vec3(hue, sat, lum); } vec3 hsl2rgb(vec3 col) { const float onethird = 1.0 / 3.0; const float twothird = 2.0 / 3.0; const float rcpsixth = 6.0; float hue = col.x; float sat = col.y; float lum = col.z; vec3 xt = vec3(0.0); if (hue < onethird) { xt.r = rcpsixth * (onethird - hue); xt.g = rcpsixth * hue; xt.b = 0.0; } else if (hue < twothird) { xt.r = 0.0; xt.g = rcpsixth * (twothird - hue); xt.b = rcpsixth * (hue - onethird); } else xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); xt = min(xt, 1.0); float sat2 = 2.0 * sat; float satinv = 1.0 - sat; float luminv = 1.0 - lum; float lum2m1 = (2.0 * lum) - 1.0; vec3 ct = (sat2 * xt) + satinv; vec3 rgb; if (lum >= 0.5) rgb = (luminv * ct) + lum2m1; else rgb = lum * ct; return rgb; } void main() { vec2 uv = v_texcoord * 2.0; vec4 sum = texture2D(tex, uv) * 4.0; sum += texture2D(tex, uv - halfpixel.xy * radius); sum += texture2D(tex, uv + halfpixel.xy * radius); sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); vec4 color = sum / 8.0; if (vibrancy == 0.0) { gl_FragColor = color; } else { // Invert it so that it correctly maps to the config setting float vibrancy_darkness1 = 1.0 - vibrancy_darkness; // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. vec3 hsl = rgb2hsl(color.rgb); // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); float b1 = b * vibrancy_darkness1; float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); gl_FragColor = vec4(newColor, color[3]); } } )#"; inline const std::string FRAGBLUR2 = R"#( #version 100 precision highp float; varying highp vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float radius; uniform vec2 halfpixel; void main() { vec2 uv = v_texcoord / 2.0; vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; gl_FragColor = sum / 12.0; } )#"; inline const std::string FRAGBLURPREPARE = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float contrast; uniform float brightness; float gain(float x, float k) { float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); return (x < 0.5) ? a : 1.0 - a; } void main() { vec4 pixColor = texture2D(tex, v_texcoord); // contrast if (contrast != 1.0) { pixColor.r = gain(pixColor.r, contrast); pixColor.g = gain(pixColor.g, contrast); pixColor.b = gain(pixColor.b, contrast); } // brightness if (brightness > 1.0) { pixColor.rgb *= brightness; } gl_FragColor = pixColor; } )#"; inline const std::string FRAGBLURFINISH = R"#( precision highp float; varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform float noise; uniform float brightness; uniform int colorize; uniform vec3 colorizeTint; uniform float boostA; float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); } void main() { vec4 pixColor = texture2D(tex, v_texcoord); // noise float noiseHash = hash(v_texcoord); float noiseAmount = (mod(noiseHash, 1.0) - 0.5); pixColor.rgb += noiseAmount * noise; // brightness if (brightness < 1.0) { pixColor.rgb *= brightness; } pixColor.a *= boostA; if (colorize == 1) { gl_FragColor = vec4(colorizeTint.r * pixColor.a, colorizeTint.g * pixColor.a, colorizeTint.b * pixColor.a, pixColor.a); return; } gl_FragColor = pixColor; } )#"; // makes a stencil without corners inline const std::string FRAGBORDER = R"#( precision highp float; varying vec4 v_color; varying vec2 v_texcoord; uniform vec2 topLeft; uniform vec2 fullSize; uniform vec2 fullSizeUntransformed; uniform float radius; uniform float radiusOuter; uniform float thick; // Gradients are in OkLabA!!!! {l, a, b, alpha} uniform vec4 gradient[10]; uniform vec4 gradient2[10]; uniform int gradientLength; uniform int gradient2Length; uniform float angle; uniform float angle2; uniform float gradientLerp; uniform float alpha; float linearToGamma(float x) { return x >= 0.0031308 ? 1.055 * pow(x, 0.416666666) - 0.055 : 12.92 * x; } vec4 okLabAToSrgb(vec4 lab) { float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); return vec4(linearToGamma(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292), linearToGamma(l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965)), linearToGamma(l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010), lab[3]); } vec4 getOkColorForCoordArray1(vec2 normalizedCoord) { if (gradientLength < 2) return gradient[0]; float finalAng = 0.0; if (angle > 4.71 /* 270 deg */) { normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = 6.28 - angle; } else if (angle > 3.14 /* 180 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = angle - 3.14; } else if (angle > 1.57 /* 90 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; finalAng = 3.14 - angle; } else { finalAng = angle; } float sine = sin(finalAng); float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); int bottom = int(floor(progress)); int top = bottom + 1; return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); } vec4 getOkColorForCoordArray2(vec2 normalizedCoord) { if (gradient2Length < 2) return gradient2[0]; float finalAng = 0.0; if (angle2 > 4.71 /* 270 deg */) { normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = 6.28 - angle; } else if (angle2 > 3.14 /* 180 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; normalizedCoord[1] = 1.0 - normalizedCoord[1]; finalAng = angle - 3.14; } else if (angle2 > 1.57 /* 90 deg */) { normalizedCoord[0] = 1.0 - normalizedCoord[0]; finalAng = 3.14 - angle2; } else { finalAng = angle2; } float sine = sin(finalAng); float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); int bottom = int(floor(progress)); int top = bottom + 1; return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); } vec4 getColorForCoord(vec2 normalizedCoord) { vec4 result1 = getOkColorForCoordArray1(normalizedCoord); if (gradient2Length <= 0) return okLabAToSrgb(result1); vec4 result2 = getOkColorForCoordArray2(normalizedCoord); return okLabAToSrgb(mix(result1, result2, gradientLerp)); } void main() { highp vec2 pixCoord = vec2(gl_FragCoord); highp vec2 pixCoordOuter = pixCoord; highp vec2 originalPixCoord = v_texcoord; originalPixCoord *= fullSizeUntransformed; float additionalAlpha = 1.0; vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); bool done = false; pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; pixCoordOuter = pixCoord; pixCoord -= fullSize * 0.5 - radius; pixCoordOuter -= fullSize * 0.5 - radiusOuter; // center the pixes dont make it top-left pixCoord += vec2(1.0, 1.0) / fullSize; pixCoordOuter += vec2(1.0, 1.0) / fullSize; if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { // smoothing constant for the edge: more = blurrier, but smoother const float SMOOTHING_CONSTANT = )#" + std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(; float dist = length(pixCoord); float distOuter = length(pixCoordOuter); float h = (thick / 2.0); if (dist < radius - h) { // lower float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); additionalAlpha *= normalized; done = true; } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { // higher float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); additionalAlpha *= normalized; done = true; } else if (distOuter < radiusOuter - h) { additionalAlpha = 1.0; done = true; } } // now check for other shit if (!done) { // distance to all straight bb borders float distanceT = originalPixCoord[1]; float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; float distanceL = originalPixCoord[0]; float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; // get the smallest float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); if (smallest > thick) discard; } if (additionalAlpha == 0.0) discard; pixColor = getColorForCoord(v_texcoord); pixColor.rgb *= pixColor[3]; pixColor *= alpha * additionalAlpha; gl_FragColor = pixColor; } )#";hyprlock-0.9.2/src/renderer/Shared.hpp000066400000000000000000000002141506744673100177030ustar00rootroot00000000000000#pragma once #include "Texture.hpp" #include "../defines.hpp" struct SPreloadedAsset { CTexture texture; bool ready = false; };hyprlock-0.9.2/src/renderer/Texture.cpp000066400000000000000000000006021506744673100201310ustar00rootroot00000000000000#include "Texture.hpp" CTexture::CTexture() { ; // naffin' } CTexture::~CTexture() { destroyTexture(); } void CTexture::destroyTexture() { if (m_bAllocated) { glDeleteTextures(1, &m_iTexID); m_iTexID = 0; } m_bAllocated = false; } void CTexture::allocate() { if (!m_bAllocated) glGenTextures(1, &m_iTexID); m_bAllocated = true; } hyprlock-0.9.2/src/renderer/Texture.hpp000066400000000000000000000010411506744673100201340ustar00rootroot00000000000000#pragma once #include #include "../helpers/Math.hpp" enum TEXTURETYPE { TEXTURE_INVALID, // Invalid TEXTURE_RGBA, // 4 channels TEXTURE_RGBX, // discard A TEXTURE_EXTERNAL, // EGLImage }; class CTexture { public: CTexture(); ~CTexture(); void destroyTexture(); void allocate(); TEXTURETYPE m_iType = TEXTURE_RGBA; GLenum m_iTarget = GL_TEXTURE_2D; bool m_bAllocated = false; GLuint m_iTexID = 0; Vector2D m_vSize; };hyprlock-0.9.2/src/renderer/mtx.hpp000066400000000000000000000133501506744673100173120ustar00rootroot00000000000000 #pragma once #include #include #include "../helpers/Box.hpp" static enum wl_output_transform wlr_output_transform_invert(enum wl_output_transform tr) { if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) { tr = (wl_output_transform)((int)tr ^ (int)WL_OUTPUT_TRANSFORM_180); } return tr; } static void wlr_matrix_identity(float mat[9]) { 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)); } static void wlr_matrix_multiply(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)); } static void wlr_matrix_transpose(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)); } static void wlr_matrix_translate(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, }; wlr_matrix_multiply(mat, mat, translate); } static void wlr_matrix_scale(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, }; wlr_matrix_multiply(mat, mat, scale); } static void wlr_matrix_rotate(float mat[9], float rad) { float rotate[9] = { cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, }; wlr_matrix_multiply(mat, mat, rotate); } static const float transforms[][9] = { [WL_OUTPUT_TRANSFORM_NORMAL] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_90] = { 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_180] = { -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_270] = { 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED] = { -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, }; static void wlr_matrix_transform(float mat[9], enum wl_output_transform transform) { wlr_matrix_multiply(mat, mat, transforms[transform]); } static void matrix_projection(float mat[9], int width, int height, enum wl_output_transform transform) { std::memset(mat, 0, sizeof(*mat) * 9); const float* t = transforms[transform]; 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; } static void wlr_matrix_project_box(float mat[9], const CBox* box, enum wl_output_transform transform, float rotation, const float projection[9]) { int x = box->x; int y = box->y; int width = box->width; int height = box->height; wlr_matrix_identity(mat); wlr_matrix_translate(mat, x, y); if (rotation != 0) { wlr_matrix_translate(mat, width / 2, height / 2); wlr_matrix_rotate(mat, rotation); wlr_matrix_translate(mat, -width / 2, -height / 2); } wlr_matrix_scale(mat, width, height); if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { wlr_matrix_translate(mat, 0.5, 0.5); wlr_matrix_transform(mat, transform); wlr_matrix_translate(mat, -0.5, -0.5); } wlr_matrix_multiply(mat, projection, mat); } static void matrixProjection(float mat[9], int w, int h, wl_output_transform tr) { memset(mat, 0, sizeof(*mat) * 9); const float* t = transforms[tr]; float x = 2.0f / w; float y = 2.0f / h; // 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; } hyprlock-0.9.2/src/renderer/widgets/000077500000000000000000000000001506744673100174355ustar00rootroot00000000000000hyprlock-0.9.2/src/renderer/widgets/Background.cpp000066400000000000000000000301631506744673100222230ustar00rootroot00000000000000#include "Background.hpp" #include "../Renderer.hpp" #include "../Framebuffer.hpp" #include "../Shared.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../core/AnimationManager.hpp" #include "../../config/ConfigManager.hpp" #include #include #include #include CBackground::CBackground() { blurredFB = makeUnique(); pendingBlurredFB = makeUnique(); transformedScFB = makeUnique(); } CBackground::~CBackground() { reset(); } void CBackground::registerSelf(const ASP& self) { m_self = self; } void CBackground::configure(const std::unordered_map& props, const SP& pOutput) { reset(); try { color = std::any_cast(props.at("color")); blurPasses = std::any_cast(props.at("blur_passes")); blurSize = std::any_cast(props.at("blur_size")); vibrancy = std::any_cast(props.at("vibrancy")); vibrancy_darkness = std::any_cast(props.at("vibrancy_darkness")); noise = std::any_cast(props.at("noise")); brightness = std::any_cast(props.at("brightness")); contrast = std::any_cast(props.at("contrast")); path = std::any_cast(props.at("path")); reloadCommand = std::any_cast(props.at("reload_cmd")); reloadTime = std::any_cast(props.at("reload_time")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CBackground: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing propperty for CBackground: {}", e.what()); // } isScreenshot = path == "screenshot"; viewport = pOutput->getViewport(); outputPort = pOutput->stringPort; transform = wlTransformToHyprutils(invertTransform(pOutput->transform)); scResourceID = CScreencopyFrame::getResourceId(pOutput); g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); // When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available. // Dynamic ones are tricky, because a screencopy would copy hyprlock itself. if (g_pAsyncResourceGatherer->gathered && !g_pAsyncResourceGatherer->getAssetByID(scResourceID)) { Debug::log(LOG, "Missing screenshot for output {}", outputPort); scResourceID = ""; } if (isScreenshot) { resourceID = scResourceID; // Fallback to solid background:color when scResourceID=="" if (!g_pHyprlock->getScreencopy()) { Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); resourceID = ""; } } else if (!path.empty()) resourceID = "background:" + path; if (!isScreenshot && reloadTime > -1) { try { modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } plantReloadTimer(); // No reloads for screenshots. } } void CBackground::reset() { if (reloadTimer) { reloadTimer->cancel(); reloadTimer.reset(); } blurredFB->destroyBuffer(); pendingBlurredFB->destroyBuffer(); } void CBackground::updatePrimaryAsset() { if (asset || resourceID.empty()) return; asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); if (!asset) return; const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->texture.m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender); if (NEEDFB) renderToFB(asset->texture, *blurredFB, blurPasses, isScreenshot); } void CBackground::updatePendingAsset() { // For crossfading a new asset if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated()) return; renderToFB(pendingAsset->texture, *pendingBlurredFB, blurPasses); } void CBackground::updateScAsset() { if (scAsset || scResourceID.empty()) return; // path=screenshot -> scAsset = asset scAsset = (asset && isScreenshot) ? asset : g_pAsyncResourceGatherer->getAssetByID(scResourceID); if (!scAsset) return; const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL; if (NEEDSCTRANSFORM) renderToFB(scAsset->texture, *transformedScFB, 0, true); } const CTexture& CBackground::getPrimaryAssetTex() const { // This case is only for background:path=screenshot with blurPasses=0 if (isScreenshot && blurPasses == 0 && transformedScFB->isAllocated()) return transformedScFB->m_cTex; return (blurredFB->isAllocated()) ? blurredFB->m_cTex : asset->texture; } const CTexture& CBackground::getPendingAssetTex() const { return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : pendingAsset->texture; } const CTexture& CBackground::getScAssetTex() const { return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : scAsset->texture; } void CBackground::renderRect(CHyprColor color) { CBox monbox = {0, 0, viewport.x, viewport.y}; g_pRenderer->renderRect(monbox, color, 0); } static void onReloadTimer(AWP ref) { if (auto PBG = ref.lock(); PBG) { PBG->onReloadTimerUpdate(); PBG->plantReloadTimer(); } } static void onAssetCallback(AWP ref) { if (auto PBG = ref.lock(); PBG) PBG->startCrossFade(); } static CBox getScaledBoxForTextureSize(const Vector2D& size, const Vector2D& viewport) { CBox texbox = {{}, size}; float scaleX = viewport.x / size.x; float scaleY = viewport.y / size.y; texbox.w *= std::max(scaleX, scaleY); texbox.h *= std::max(scaleX, scaleY); if (scaleX > scaleY) texbox.y = -(texbox.h - viewport.y) / 2.f; else texbox.x = -(texbox.w - viewport.x) / 2.f; texbox.round(); return texbox; } void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, bool applyTransform) { if (firstRender) firstRender = false; // make it brah Vector2D size = tex.m_vSize; if (applyTransform && transform % 2 == 1) { size.x = tex.m_vSize.y; size.y = tex.m_vSize.x; } const auto TEXBOX = getScaledBoxForTextureSize(size, viewport); if (!fb.isAllocated()) fb.alloc(viewport.x, viewport.y); // TODO 10 bit fb.bind(); g_pRenderer->renderTexture(TEXBOX, tex, 1.0, 0, applyTransform ? transform : HYPRUTILS_TRANSFORM_NORMAL); if (blurPasses > 0) g_pRenderer->blurFB(fb, CRenderer::SBlurParams{ .size = blurSize, .passes = passes, .noise = noise, .contrast = contrast, .brightness = brightness, .vibrancy = vibrancy, .vibrancy_darkness = vibrancy_darkness, }); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } bool CBackground::draw(const SRenderData& data) { updatePrimaryAsset(); updatePendingAsset(); updateScAsset(); if (asset && asset->texture.m_iType == TEXTURE_INVALID) { g_pAsyncResourceGatherer->unloadAsset(asset); resourceID = ""; renderRect(color); return false; } if (!asset || resourceID.empty()) { // fade in/out with a solid color if (data.opacity < 1.0 && scAsset) { const auto& SCTEX = getScAssetTex(); const auto SCTEXBOX = getScaledBoxForTextureSize(SCTEX.m_vSize, viewport); g_pRenderer->renderTexture(SCTEXBOX, SCTEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); CHyprColor col = color; col.a *= data.opacity; renderRect(col); return true; } renderRect(color); return !asset && !resourceID.empty(); // resource not ready } const auto& TEX = getPrimaryAssetTex(); const auto TEXBOX = getScaledBoxForTextureSize(TEX.m_vSize, viewport); if (data.opacity < 1.0 && scAsset) { const auto& SCTEX = getScAssetTex(); g_pRenderer->renderTextureMix(TEXBOX, SCTEX, TEX, 1.0, data.opacity, 0); } else if (crossFadeProgress->isBeingAnimated()) { const auto& PENDINGTEX = getPendingAssetTex(); g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0); } else g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0); return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0; } void CBackground::plantReloadTimer() { if (reloadTime == 0) reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true); else if (reloadTime > 0) reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true); } void CBackground::onReloadTimerUpdate() { const std::string OLDPATH = path; // Path parsing and early returns if (!reloadCommand.empty()) { path = spawnSync(reloadCommand); if (path.ends_with('\n')) path.pop_back(); if (path.starts_with("file://")) path = path.substr(7); if (path.empty()) return; } try { const auto MTIME = std::filesystem::last_write_time(absolutePath(path, "")); if (OLDPATH == path && MTIME == modificationTime) return; modificationTime = MTIME; } catch (std::exception& e) { path = OLDPATH; Debug::log(ERR, "{}", e.what()); return; } if (!pendingResourceID.empty()) return; // Issue the next request request.id = std::string{"background:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count()); pendingResourceID = request.id; request.asset = path; request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE; request.callback = [REF = m_self]() { onAssetCallback(REF); }; g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); } void CBackground::startCrossFade() { auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { if (newAsset->texture.m_iType == TEXTURE_INVALID) { g_pAsyncResourceGatherer->unloadAsset(newAsset); Debug::log(ERR, "New asset had an invalid texture!"); pendingResourceID = ""; } else if (resourceID != pendingResourceID) { pendingAsset = newAsset; crossFadeProgress->setValueAndWarp(0); *crossFadeProgress = 1.0; crossFadeProgress->setCallbackOnEnd( [REF = m_self](auto) { if (const auto PSELF = REF.lock()) { if (PSELF->asset) g_pAsyncResourceGatherer->unloadAsset(PSELF->asset); PSELF->asset = PSELF->pendingAsset; PSELF->pendingAsset = nullptr; PSELF->resourceID = PSELF->pendingResourceID; PSELF->pendingResourceID = ""; PSELF->blurredFB->destroyBuffer(); PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); } }, true); g_pHyprlock->renderOutput(outputPort); } } else if (!pendingResourceID.empty()) { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr); } } hyprlock-0.9.2/src/renderer/widgets/Background.hpp000066400000000000000000000063261506744673100222340ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "IWidget.hpp" #include "../../helpers/AnimatedVariable.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "../Framebuffer.hpp" #include "../AsyncResourceGatherer.hpp" #include #include #include #include #include #include #include struct SPreloadedAsset; class COutput; class CBackground : public IWidget { public: CBackground(); ~CBackground(); void registerSelf(const ASP& self); virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); void reset(); // Unload assets, remove timers, etc. void updatePrimaryAsset(); void updatePendingAsset(); void updateScAsset(); const CTexture& getPrimaryAssetTex() const; const CTexture& getPendingAssetTex() const; const CTexture& getScAssetTex() const; void renderRect(CHyprColor color); void renderToFB(const CTexture& text, CFramebuffer& fb, int passes, bool applyTransform = false); void onReloadTimerUpdate(); void plantReloadTimer(); void startCrossFade(); private: AWP m_self; // if needed UP blurredFB; UP pendingBlurredFB; UP transformedScFB; int blurSize = 10; int blurPasses = 3; float noise = 0.0117; float contrast = 0.8916; float brightness = 0.8172; float vibrancy = 0.1696; float vibrancy_darkness = 0.0; Vector2D viewport; std::string path = ""; std::string outputPort; Hyprutils::Math::eTransform transform; std::string resourceID; std::string scResourceID; std::string pendingResourceID; PHLANIMVAR crossFadeProgress; CHyprColor color; ASP asset = nullptr; ASP scAsset = nullptr; ASP pendingAsset = nullptr; bool isScreenshot = false; bool firstRender = true; int reloadTime = -1; std::string reloadCommand; CAsyncResourceGatherer::SPreloadRequest request; ASP reloadTimer; std::filesystem::file_time_type modificationTime; }; hyprlock-0.9.2/src/renderer/widgets/IWidget.cpp000066400000000000000000000227661506744673100215120ustar00rootroot00000000000000#include "IWidget.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "../../auth/Auth.hpp" #include #include #include #include #include using namespace Hyprutils::String; #if defined(_LIBCPP_VERSION) #pragma comment(lib, "date-tz") #include namespace std { namespace chrono { using date::current_zone; using date::locate_zone; using date::time_zone; } } #endif static Vector2D rotateVector(const Vector2D& vec, const double& ang) { const double COS = std::abs(std::cos(ang)); const double SIN = std::abs(std::sin(ang)); return Vector2D((vec.x * COS) + (vec.y * SIN), (vec.x * SIN) + (vec.y * COS)); } Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang) { // offset after rotation for alignment Vector2D rot; if (ang != 0) rot = (size - rotateVector(size, ang)) / 2.0; Vector2D pos = offset; if (halign == "center") pos.x += viewport.x / 2.0 - size.x / 2.0; else if (halign == "left") pos.x += 0 - rot.x; else if (halign == "right") pos.x += viewport.x - size.x + rot.x; else if (halign != "none") Debug::log(ERR, "IWidget: invalid halign {}", halign); if (valign == "center") pos.y += viewport.y / 2.0 - size.y / 2.0; else if (valign == "top") pos.y += viewport.y - size.y + rot.y; else if (valign == "bottom") pos.y += 0 - rot.y; else if (valign != "none") Debug::log(ERR, "IWidget: invalid valign {}", valign); return pos; } int IWidget::roundingForBox(const CBox& box, int roundingConfig) { const int MINHALFBOX = std::min(box.w, box.h) / 2.0; if (roundingConfig == -1) return MINHALFBOX; return std::clamp(roundingConfig, 0, MINHALFBOX); } int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness) { const int MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0; if (roundingConfig == -1) return MINHALFBORDER; else if (roundingConfig == 0) return 0; return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER); } static void replaceAllAttempts(std::string& str) { const size_t ATTEMPTS = g_pAuth->getFailedAttempts(); const std::string STR = std::to_string(ATTEMPTS); size_t pos = 0; while ((pos = str.find("$ATTEMPTS", pos)) != std::string::npos) { if (str.substr(pos, 10).ends_with('[') && str.substr(pos).contains(']')) { const std::string REPL = str.substr(pos + 10, str.find_first_of(']', pos) - 10 - pos); if (ATTEMPTS == 0) { str.replace(pos, 11 + REPL.length(), REPL); pos += REPL.length(); } else { str.replace(pos, 11 + REPL.length(), STR); pos += STR.length(); } } else { str.replace(pos, 9, STR); pos += STR.length(); } } } static void replaceAllLayout(std::string& str) { std::string layoutName = "error"; const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout; if (g_pSeatManager->m_pXKBKeymap) { const auto PNAME = xkb_keymap_layout_get_name(g_pSeatManager->m_pXKBKeymap, LAYOUTIDX); if (PNAME) layoutName = PNAME; } size_t pos = 0; while ((pos = str.find("$LAYOUT", pos)) != std::string::npos) { if (str.substr(pos, 8).ends_with('[') && str.substr(pos).contains(']')) { const std::string REPL = str.substr(pos + 8, str.find_first_of(']', pos) - 8 - pos); const CVarList LANGS(REPL); if (LAYOUTIDX >= LANGS.size()) { Debug::log(ERR, "Layout index {} out of bounds. Max is {}.", LAYOUTIDX, LANGS.size() - 1); return; } const std::string LANG = LANGS[LAYOUTIDX].empty() ? layoutName : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX]; str.replace(pos, 9 + REPL.length(), LANG); pos += LANG.length(); } else { str.replace(pos, 7, layoutName); pos += layoutName.length(); } } } static bool logMissingTzOnce = true; static std::chrono::hh_mm_ss getTime() { const std::chrono::time_zone* pCurrentTz = nullptr; try { auto name = std::getenv("TZ"); if (name) pCurrentTz = std::chrono::locate_zone(name); } catch (std::runtime_error&) { Debug::log(WARN, "Invalid TZ value. Falling back to current timezone!"); } if (!pCurrentTz) pCurrentTz = std::chrono::current_zone(); const auto TPNOW = std::chrono::system_clock::now(); // std::chrono::hh_mm_ss hhmmss; if (!pCurrentTz) { if (logMissingTzOnce) { Debug::log(WARN, "Current timezone unknown. Falling back to UTC!"); logMissingTzOnce = false; } hhmmss = std::chrono::hh_mm_ss{TPNOW - std::chrono::floor(TPNOW)}; } else hhmmss = std::chrono::hh_mm_ss{pCurrentTz->to_local(TPNOW) - std::chrono::floor(pCurrentTz->to_local(TPNOW))}; return hhmmss; } static std::string getTime24h() { const auto HHMMSS = getTime(); const auto HRS = HHMMSS.hours().count(); const auto MINS = HHMMSS.minutes().count(); return (HRS < 10 ? "0" : "") + std::to_string(HRS) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS); } static std::string getTime12h() { const auto HHMMSS = getTime(); const auto HRS = HHMMSS.hours().count(); const auto MINS = HHMMSS.minutes().count(); return (HRS == 12 || HRS == 0 ? "12" : (HRS % 12 < 10 ? "0" : "") + std::to_string(HRS % 12)) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS) + (HRS < 12 ? " AM" : " PM"); } IWidget::SFormatResult IWidget::formatString(std::string in) { auto uidPassword = getpwuid(getuid()); char* username = uidPassword->pw_name; char* user_gecos = uidPassword->pw_gecos; if (!username) Debug::log(ERR, "Error in formatString, username null. Errno: ", errno); if (!user_gecos) Debug::log(WARN, "Error in formatString, user_gecos null. Errno: ", errno); IWidget::SFormatResult result; replaceInString(in, "$DESC", std::string{user_gecos ? user_gecos : ""}); replaceInString(in, "$USER", std::string{username ? username : ""}); replaceInString(in, "
", std::string{"\n"}); if (in.contains("$TIME12")) { replaceInString(in, "$TIME12", getTime12h()); result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } if (in.contains("$TIME")) { replaceInString(in, "$TIME", getTime24h()); result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } if (in.contains("$ATTEMPTS")) { replaceAllAttempts(in); result.allowForceUpdate = true; } if (in.contains("$LAYOUT")) { replaceAllLayout(in); result.allowForceUpdate = true; } if (in.contains("$FAIL")) { const auto FAIL = g_pAuth->getCurrentFailText(); replaceInString(in, "$FAIL", FAIL); result.allowForceUpdate = true; } if (in.contains("$PAMFAIL")) { const auto FAIL = g_pAuth->getFailText(AUTH_IMPL_PAM); replaceInString(in, "$PAMFAIL", FAIL.value_or("")); result.allowForceUpdate = true; } if (in.contains("$PAMPROMPT")) { const auto PROMPT = g_pAuth->getPrompt(AUTH_IMPL_PAM); replaceInString(in, "$PAMPROMPT", PROMPT.value_or("")); result.allowForceUpdate = true; } if (in.contains("$FPRINTFAIL")) { const auto FPRINTFAIL = g_pAuth->getFailText(AUTH_IMPL_FINGERPRINT); replaceInString(in, "$FPRINTFAIL", FPRINTFAIL.value_or("")); result.allowForceUpdate = true; } if (in.contains("$FPRINTPROMPT")) { const auto FPRINTPROMPT = g_pAuth->getPrompt(AUTH_IMPL_FINGERPRINT); replaceInString(in, "$FPRINTPROMPT", FPRINTPROMPT.value_or("")); result.allowForceUpdate = true; } if (in.starts_with("cmd[") && in.contains("]")) { // this is a command CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true); for (const auto& v : vars) { if (v.starts_with("update:")) { try { if (v.substr(7).contains(':')) { auto str = v.substr(v.substr(7).find_first_of(':') + 8); result.allowForceUpdate = str == "true" || std::stoull(str) == 1; } result.updateEveryMs = std::stoull(v.substr(7)); } catch (std::exception& e) { Debug::log(ERR, "Error parsing {} in cmd[]", v); } } else { Debug::log(ERR, "Unknown prop in string format {}", v); } } result.alwaysUpdate = true; in = in.substr(in.find_first_of(']') + 1); result.cmd = true; } result.formatted = in; return result; } void IWidget::setHover(bool hover) { hovered = hover; } bool IWidget::isHovered() const { return hovered; } bool IWidget::containsPoint(const Vector2D& pos) const { return getBoundingBoxWl().containsPoint(pos); } hyprlock-0.9.2/src/renderer/widgets/IWidget.hpp000066400000000000000000000031331506744673100215020ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "../../helpers/Math.hpp" #include #include #include class COutput; class IWidget { public: struct SRenderData { float opacity = 1; }; virtual ~IWidget() = default; virtual void configure(const std::unordered_map& prop, const SP& pOutput) = 0; virtual bool draw(const SRenderData& data) = 0; static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang = 0); static int roundingForBox(const CBox& box, int roundingConfig); static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness); virtual CBox getBoundingBoxWl() const { return CBox(); }; virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {} virtual void onHover(const Vector2D& pos) {} bool containsPoint(const Vector2D& pos) const; struct SFormatResult { std::string formatted; float updateEveryMs = 0; // 0 means don't (static) bool alwaysUpdate = false; bool cmd = false; bool allowForceUpdate = false; }; static SFormatResult formatString(std::string in); void setHover(bool hover); bool isHovered() const; private: bool hovered = false; }; hyprlock-0.9.2/src/renderer/widgets/Image.cpp000066400000000000000000000201461506744673100211660ustar00rootroot00000000000000#include "Image.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" #include #include #include CImage::~CImage() { reset(); } void CImage::registerSelf(const ASP& self) { m_self = self; } static void onTimer(AWP ref) { if (auto PIMAGE = ref.lock(); PIMAGE) { PIMAGE->onTimerUpdate(); PIMAGE->plantTimer(); } } static void onAssetCallback(AWP ref) { if (auto PIMAGE = ref.lock(); PIMAGE) PIMAGE->renderUpdate(); } void CImage::onTimerUpdate() { const std::string OLDPATH = path; if (!reloadCommand.empty()) { path = spawnSync(reloadCommand); if (path.ends_with('\n')) path.pop_back(); if (path.starts_with("file://")) path = path.substr(7); if (path.empty()) return; } try { const auto MTIME = std::filesystem::last_write_time(absolutePath(path, "")); if (OLDPATH == path && MTIME == modificationTime) return; modificationTime = MTIME; } catch (std::exception& e) { path = OLDPATH; Debug::log(ERR, "{}", e.what()); return; } if (!pendingResourceID.empty()) return; request.id = std::string{"image:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count()); pendingResourceID = request.id; request.asset = path; request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE; request.callback = [REF = m_self]() { onAssetCallback(REF); }; g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); } void CImage::plantTimer() { if (reloadTime == 0) { imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, true); } else if (reloadTime > 0) imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, false); } void CImage::configure(const std::unordered_map& props, const SP& pOutput) { reset(); viewport = pOutput->getViewport(); stringPort = pOutput->stringPort; shadow.configure(m_self, props, viewport); try { size = std::any_cast(props.at("size")); rounding = std::any_cast(props.at("rounding")); border = std::any_cast(props.at("border_size")); color = *CGradientValueData::fromAnyPv(props.at("border_color")); configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); angle = std::any_cast(props.at("rotate")); path = std::any_cast(props.at("path")); reloadTime = std::any_cast(props.at("reload_time")); reloadCommand = std::any_cast(props.at("reload_cmd")); onclickCommand = std::any_cast(props.at("onclick")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CImage: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing propperty for CImage: {}", e.what()); // } resourceID = "image:" + path; angle = angle * M_PI / 180.0; if (reloadTime > -1) { try { modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } plantTimer(); } } void CImage::reset() { if (imageTimer) { imageTimer->cancel(); imageTimer.reset(); } if (g_pHyprlock->m_bTerminate) return; imageFB.destroyBuffer(); if (asset && reloadTime > -1) // Don't unload asset if it's a static image g_pAsyncResourceGatherer->unloadAsset(asset); asset = nullptr; pendingResourceID = ""; resourceID = ""; } bool CImage::draw(const SRenderData& data) { if (resourceID.empty()) return false; if (!asset) asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); if (!asset) return true; if (asset->texture.m_iType == TEXTURE_INVALID) { g_pAsyncResourceGatherer->unloadAsset(asset); resourceID = ""; return false; } if (!imageFB.isAllocated()) { const Vector2D IMAGEPOS = {border, border}; const Vector2D BORDERPOS = {0.0, 0.0}; const Vector2D TEXSIZE = asset->texture.m_vSize; const float SCALEX = size / TEXSIZE.x; const float SCALEY = size / TEXSIZE.y; // image with borders offset, with extra pixel for anti-aliasing when rotated CBox texbox = {angle == 0 ? IMAGEPOS : IMAGEPOS + Vector2D{1.0, 1.0}, TEXSIZE}; texbox.w *= std::max(SCALEX, SCALEY); texbox.h *= std::max(SCALEX, SCALEY); // plus borders if any CBox borderBox = {angle == 0 ? BORDERPOS : BORDERPOS + Vector2D{1.0, 1.0}, texbox.size() + IMAGEPOS * 2.0}; borderBox.round(); const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0}; const int ROUND = roundingForBox(texbox, rounding); const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border); imageFB.alloc(FBSIZE.x, FBSIZE.y, true); g_pRenderer->pushFb(imageFB.m_iFb); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); if (border > 0) g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0); texbox.round(); g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL); g_pRenderer->popFb(); } CTexture* tex = &imageFB.m_cTex; CBox texbox = {{}, tex->m_vSize}; if (firstRender) { firstRender = false; shadow.markShadowDirty(); } shadow.draw(data); pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle); texbox.x = pos.x; texbox.y = pos.y; texbox.round(); texbox.rot = angle; g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; } void CImage::renderUpdate() { auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { if (newAsset->texture.m_iType == TEXTURE_INVALID) { g_pAsyncResourceGatherer->unloadAsset(newAsset); } else if (resourceID != pendingResourceID) { g_pAsyncResourceGatherer->unloadAsset(asset); imageFB.destroyBuffer(); asset = newAsset; resourceID = pendingResourceID; firstRender = true; } pendingResourceID = ""; } else if (!pendingResourceID.empty()) { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); pendingResourceID = ""; } else if (!pendingResourceID.empty()) { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr); } g_pHyprlock->renderOutput(stringPort); } CBox CImage::getBoundingBoxWl() const { if (!imageFB.isAllocated()) return CBox{}; return { Vector2D{pos.x, viewport.y - pos.y - imageFB.m_cTex.m_vSize.y}, imageFB.m_cTex.m_vSize, }; } void CImage::onClick(uint32_t button, bool down, const Vector2D& pos) { if (down && !onclickCommand.empty()) spawnAsync(onclickCommand); } void CImage::onHover(const Vector2D& pos) { if (!onclickCommand.empty()) g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER); } hyprlock-0.9.2/src/renderer/widgets/Image.hpp000066400000000000000000000044171506744673100211760ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "IWidget.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" #include "Shadowable.hpp" #include #include #include #include struct SPreloadedAsset; class COutput; class CImage : public IWidget { public: CImage() = default; ~CImage(); void registerSelf(const ASP& self); virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); void reset(); void renderUpdate(); void onTimerUpdate(); void plantTimer(); private: AWP m_self; CFramebuffer imageFB; int size; int rounding; double border; double angle; CGradientValueData color; Vector2D pos; Vector2D configPos; std::string halign, valign, path; bool firstRender = true; int reloadTime; std::string reloadCommand; std::string onclickCommand; std::filesystem::file_time_type modificationTime; ASP imageTimer; CAsyncResourceGatherer::SPreloadRequest request; Vector2D viewport; std::string stringPort; std::string resourceID; std::string pendingResourceID; // if reloading image ASP asset = nullptr; CShadowable shadow; }; hyprlock-0.9.2/src/renderer/widgets/Label.cpp000066400000000000000000000142301506744673100211600ustar00rootroot00000000000000#include "Label.hpp" #include "../Renderer.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" #include #include CLabel::~CLabel() { reset(); } void CLabel::registerSelf(const ASP& self) { m_self = self; } static void onTimer(AWP ref) { if (auto PLABEL = ref.lock(); PLABEL) { // update label PLABEL->onTimerUpdate(); // plant new timer PLABEL->plantTimer(); } } static void onAssetCallback(AWP ref) { if (auto PLABEL = ref.lock(); PLABEL) PLABEL->renderUpdate(); } std::string CLabel::getUniqueResourceId() { return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); } void CLabel::onTimerUpdate() { std::string oldFormatted = label.formatted; label = formatString(labelPreFormat); if (label.formatted == oldFormatted && !label.alwaysUpdate) return; if (!pendingResourceID.empty()) { Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID); return; } // request new request.id = getUniqueResourceId(); pendingResourceID = request.id; request.asset = label.formatted; request.callback = [REF = m_self]() { onAssetCallback(REF); }; g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); } void CLabel::plantTimer() { if (label.updateEveryMs != 0) labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), [REF = m_self](auto, auto) { onTimer(REF); }, this, label.allowForceUpdate); else if (label.updateEveryMs == 0 && label.allowForceUpdate) labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, this, true); } void CLabel::configure(const std::unordered_map& props, const SP& pOutput) { reset(); outputStringPort = pOutput->stringPort; viewport = pOutput->getViewport(); shadow.configure(m_self, props, viewport); try { configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport); labelPreFormat = std::any_cast(props.at("text")); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); angle = std::any_cast(props.at("rotate")); angle = angle * M_PI / 180.0; onclickCommand = std::any_cast(props.at("onclick")); std::string textAlign = std::any_cast(props.at("text_align")); std::string fontFamily = std::any_cast(props.at("font_family")); CHyprColor labelColor = std::any_cast(props.at("color")); int fontSize = std::any_cast(props.at("font_size")); label = formatString(labelPreFormat); request.id = getUniqueResourceId(); resourceID = request.id; request.asset = label.formatted; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = fontFamily; request.props["color"] = labelColor; request.props["font_size"] = fontSize; request.props["cmd"] = label.cmd; if (!textAlign.empty()) request.props["text_align"] = textAlign; } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CLabel: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing property for CLabel: {}", e.what()); // } pos = configPos; // Label size not known yet g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); plantTimer(); } void CLabel::reset() { if (labelTimer) { labelTimer->cancel(); labelTimer.reset(); } if (g_pHyprlock->m_bTerminate) return; if (asset) g_pAsyncResourceGatherer->unloadAsset(asset); asset = nullptr; pendingResourceID.clear(); resourceID.clear(); } bool CLabel::draw(const SRenderData& data) { if (!asset) { asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); if (!asset) return true; } if (updateShadow) { updateShadow = false; shadow.markShadowDirty(); } shadow.draw(data); // calc pos pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle); CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y}; box.rot = angle; g_pRenderer->renderTexture(box, asset->texture, data.opacity); return false; } void CLabel::renderUpdate() { auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { // new asset is ready :D g_pAsyncResourceGatherer->unloadAsset(asset); asset = newAsset; resourceID = pendingResourceID; pendingResourceID = ""; updateShadow = true; } else { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr); return; } g_pHyprlock->renderOutput(outputStringPort); } CBox CLabel::getBoundingBoxWl() const { if (!asset) return CBox{}; return { Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y}, asset->texture.m_vSize, }; } void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) { if (down && !onclickCommand.empty()) spawnAsync(onclickCommand); } void CLabel::onHover(const Vector2D& pos) { if (!onclickCommand.empty()) g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER); } hyprlock-0.9.2/src/renderer/widgets/Label.hpp000066400000000000000000000036711506744673100211740ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "IWidget.hpp" #include "Shadowable.hpp" #include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" #include #include #include struct SPreloadedAsset; class CSessionLockSurface; class CLabel : public IWidget { public: CLabel() = default; ~CLabel(); void registerSelf(const ASP& self); virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); void reset(); void renderUpdate(); void onTimerUpdate(); void plantTimer(); private: AWP m_self; std::string getUniqueResourceId(); std::string labelPreFormat; IWidget::SFormatResult label; Vector2D viewport; Vector2D pos; Vector2D configPos; double angle; std::string resourceID; std::string pendingResourceID; // if dynamic label std::string halign, valign; std::string onclickCommand; ASP asset = nullptr; std::string outputStringPort; CAsyncResourceGatherer::SPreloadRequest request; ASP labelTimer = nullptr; CShadowable shadow; bool updateShadow = true; }; hyprlock-0.9.2/src/renderer/widgets/PasswordInputField.cpp000066400000000000000000000470301506744673100237330ustar00rootroot00000000000000#include "PasswordInputField.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" #include "../../auth/Auth.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../config/ConfigManager.hpp" #include "../../helpers/Log.hpp" #include "../../core/AnimationManager.hpp" #include "../../helpers/Color.hpp" #include #include #include #include #include using namespace Hyprutils::String; CPasswordInputField::~CPasswordInputField() { reset(); } void CPasswordInputField::registerSelf(const ASP& self) { m_self = self; } void CPasswordInputField::configure(const std::unordered_map& props, const SP& pOutput) { reset(); outputStringPort = pOutput->stringPort; viewport = pOutput->getViewport(); shadow.configure(m_self, props, viewport); try { pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport); configSize = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); outThick = std::any_cast(props.at("outline_thickness")); dots.size = std::any_cast(props.at("dots_size")); dots.spacing = std::any_cast(props.at("dots_spacing")); dots.center = std::any_cast(props.at("dots_center")); dots.rounding = std::any_cast(props.at("dots_rounding")); dots.textFormat = std::any_cast(props.at("dots_text_format")); fadeOnEmpty = std::any_cast(props.at("fade_on_empty")); fadeTimeoutMs = std::any_cast(props.at("fade_timeout")); hiddenInputState.enabled = std::any_cast(props.at("hide_input")); rounding = std::any_cast(props.at("rounding")); configPlaceholderText = std::any_cast(props.at("placeholder_text")); configFailText = std::any_cast(props.at("fail_text")); fontFamily = std::any_cast(props.at("font_family")); colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color")); colorConfig.inner = std::any_cast(props.at("inner_color")); colorConfig.font = std::any_cast(props.at("font_color")); colorConfig.fail = CGradientValueData::fromAnyPv(props.at("fail_color")); colorConfig.check = CGradientValueData::fromAnyPv(props.at("check_color")); colorConfig.both = CGradientValueData::fromAnyPv(props.at("bothlock_color")); colorConfig.caps = CGradientValueData::fromAnyPv(props.at("capslock_color")); colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color")); colorConfig.invertNum = std::any_cast(props.at("invert_numlock")); colorConfig.swapFont = std::any_cast(props.at("swap_font_color")); colorConfig.hiddenBase = std::any_cast(props.at("hide_input_base_color")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CPasswordInputField: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing property for CPasswordInputField: {}", e.what()); // } configPos = pos; colorState.font = colorConfig.font; pos = posFromHVAlign(viewport, configSize, pos, halign, valign); dots.size = std::clamp(dots.size, 0.2f, 0.8f); dots.spacing = std::clamp(dots.spacing, -1.f, 1.f); colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps; g_pAnimationManager->createAnimation(0.f, fade.a, g_pConfigManager->m_AnimationTree.getConfig("inputFieldFade")); g_pAnimationManager->createAnimation(0.f, dots.currentAmount, g_pConfigManager->m_AnimationTree.getConfig("inputFieldDots")); g_pAnimationManager->createAnimation(configSize, size, g_pConfigManager->m_AnimationTree.getConfig("inputFieldWidth")); g_pAnimationManager->createAnimation(colorConfig.inner, colorState.inner, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors")); g_pAnimationManager->createAnimation(*colorConfig.outer, colorState.outer, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors")); srand(std::chrono::system_clock::now().time_since_epoch().count()); pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign); if (!dots.textFormat.empty()) { dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat); CAsyncResourceGatherer::SPreloadRequest request; request.id = dots.textResourceID; request.asset = dots.textFormat; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = fontFamily; request.props["color"] = colorConfig.font; request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f); g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); } // request the inital placeholder asset updatePlaceholder(); } void CPasswordInputField::reset() { if (fade.fadeOutTimer.get()) { fade.fadeOutTimer->cancel(); fade.fadeOutTimer.reset(); } if (g_pHyprlock->m_bTerminate) return; if (placeholder.asset) g_pAsyncResourceGatherer->unloadAsset(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID.clear(); placeholder.currentText.clear(); } static void fadeOutCallback(AWP ref) { if (const auto PP = ref.lock(); PP) PP->onFadeOutTimer(); } void CPasswordInputField::onFadeOutTimer() { fade.allowFadeOut = true; fade.fadeOutTimer.reset(); g_pHyprlock->renderOutput(outputStringPort); } void CPasswordInputField::updateFade() { if (!fadeOnEmpty) { fade.a->setValueAndWarp(1.0); return; } const bool INPUTUSED = passwordLength > 0 || checkWaiting; if (INPUTUSED && fade.allowFadeOut) fade.allowFadeOut = false; if (INPUTUSED && fade.fadeOutTimer.get()) { fade.fadeOutTimer->cancel(); fade.fadeOutTimer.reset(); } if (!INPUTUSED && fade.a->goal() != 0.0) { if (fade.allowFadeOut || fadeTimeoutMs == 0) { *fade.a = 0.0; fade.allowFadeOut = false; } else if (!fade.fadeOutTimer.get()) fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr); } else if (INPUTUSED && fade.a->goal() != 1.0) *fade.a = 1.0; if (fade.a->isBeingAnimated()) redrawShadow = true; } void CPasswordInputField::updateDots() { if (dots.currentAmount->goal() == passwordLength) return; if (checkWaiting) return; if (passwordLength == 0) dots.currentAmount->setValueAndWarp(passwordLength); else *dots.currentAmount = passwordLength; } bool CPasswordInputField::draw(const SRenderData& data) { if (firstRender || redrawShadow) { firstRender = false; redrawShadow = false; shadow.markShadowDirty(); } bool forceReload = false; passwordLength = g_pHyprlock->getPasswordBufferDisplayLen(); checkWaiting = g_pAuth->checkWaiting(); displayFail = g_pAuth->m_bDisplayFailText; updateFade(); updateDots(); updateColors(); updatePlaceholder(); updateWidth(); updateHiddenInputState(); CBox inputFieldBox = {pos, size->value()}; CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}}; SRenderData shadowData = data; shadowData.opacity *= fade.a->value(); if (!size->isBeingAnimated()) shadow.draw(shadowData); //CGradientValueData outerGrad = colorState.outer->value(); //for (auto& c : outerGrad.m_vColors) // c.a *= fade.a->value() * data.opacity; CHyprColor innerCol = colorState.inner->value(); innerCol.a *= fade.a->value() * data.opacity; CHyprColor fontCol = colorState.font; fontCol.a *= fade.a->value() * data.opacity; if (outThick > 0) { const auto OUTERROUND = roundingForBorderBox(outerBox, rounding, outThick); g_pRenderer->renderBorder(outerBox, colorState.outer->value(), outThick, OUTERROUND, fade.a->value() * data.opacity); if (passwordLength != 0 && !checkWaiting && hiddenInputState.enabled) { CBox outerBoxScaled = outerBox; Vector2D p = outerBox.pos(); outerBoxScaled.translate(-p).scale(0.5).translate(p); if (hiddenInputState.lastQuadrant > 1) outerBoxScaled.y += outerBoxScaled.h; if (hiddenInputState.lastQuadrant % 2 == 1) outerBoxScaled.x += outerBoxScaled.w; glEnable(GL_SCISSOR_TEST); glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h); g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity); glScissor(0, 0, viewport.x, viewport.y); glDisable(GL_SCISSOR_TEST); } } const int ROUND = roundingForBox(inputFieldBox, rounding); g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND); if (!hiddenInputState.enabled) { const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f; Vector2D passSize{RECTPASSSIZE, RECTPASSSIZE}; int passSpacing = std::floor(passSize.x * dots.spacing); if (!dots.textFormat.empty()) { if (!dots.textAsset) dots.textAsset = g_pAsyncResourceGatherer->getAssetByID(dots.textResourceID); if (!dots.textAsset) forceReload = true; else { passSize = dots.textAsset->texture.m_vSize; passSpacing = std::floor(passSize.x * dots.spacing); } } const auto CURRDOTS = dots.currentAmount->value(); const double DOTPAD = (inputFieldBox.h - passSize.y) / 2.0; const double DOTAREAWIDTH = inputFieldBox.w - (DOTPAD * 2); const int MAXDOTS = std::round(DOTAREAWIDTH * 1.0 / (passSize.x + passSpacing)); const int DOTFLOORED = std::floor(CURRDOTS); const auto DOTALPHA = fontCol.a; // Calculate the total width required for all dots including spaces between them const double CURRWIDTH = ((passSize.x + passSpacing) * CURRDOTS) - passSpacing; // Calculate starting x-position to ensure dots stay centered within the input field double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD : DOTPAD; if (CURRDOTS > MAXDOTS) xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2.0; if (dots.rounding == -1) dots.rounding = passSize.x / 2.0; else if (dots.rounding == -2) dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size; for (int i = 0; i < CURRDOTS; ++i) { if (i < DOTFLOORED - MAXDOTS) continue; if (CURRDOTS != DOTFLOORED) { if (i == DOTFLOORED) fontCol.a *= (CURRDOTS - DOTFLOORED) * data.opacity; else if (i == DOTFLOORED - MAXDOTS) fontCol.a *= (1 - CURRDOTS + DOTFLOORED) * data.opacity; } Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart + (i * (passSize.x + passSpacing)), (inputFieldBox.h / 2.0) - (passSize.y / 2.0)}; CBox box{dotPosition, passSize}; if (!dots.textFormat.empty()) { if (!dots.textAsset) { forceReload = true; fontCol.a = DOTALPHA; break; } g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding); } else g_pRenderer->renderRect(box, fontCol, dots.rounding); fontCol.a = DOTALPHA; } } if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) { ASP currAsset = nullptr; if (!placeholder.asset) placeholder.asset = g_pAsyncResourceGatherer->getAssetByID(placeholder.resourceID); currAsset = placeholder.asset; if (currAsset) { const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->texture.m_vSize / 2.0; const CBox ASSETBOX{ASSETPOS, currAsset->texture.m_vSize}; // Cut the texture to the width of the input field glEnable(GL_SCISSOR_TEST); glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h); g_pRenderer->renderTexture(ASSETBOX, currAsset->texture, data.opacity * fade.a->value(), 0); glScissor(0, 0, viewport.x, viewport.y); glDisable(GL_SCISSOR_TEST); } else forceReload = true; } return redrawShadow || forceReload; } void CPasswordInputField::updatePlaceholder() { if (passwordLength != 0) { if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) { std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); g_pAsyncResourceGatherer->unloadAsset(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID = ""; redrawShadow = true; } return; } // already requested a placeholder for the current fail if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts()) return; placeholder.failedAttempts = g_pAuth->getFailedAttempts(); std::string newText = (displayFail) ? formatString(configFailText).formatted : formatString(configPlaceholderText).formatted; // if the text is unchanged we don't need to do anything, unless we are swapping font color const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont; if (!ALLOWCOLORSWAP && newText == placeholder.currentText) return; const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a); if (placeholder.resourceID == NEWRESOURCEID) return; Debug::log(TRACE, "Updating placeholder text: {}", newText); placeholder.currentText = newText; placeholder.asset = nullptr; placeholder.resourceID = NEWRESOURCEID; if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end()) return; Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID); placeholder.registeredResourceIDs.push_back(placeholder.resourceID); // query CAsyncResourceGatherer::SPreloadRequest request; request.id = placeholder.resourceID; request.asset = placeholder.currentText; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = fontFamily; request.props["color"] = colorState.font; request.props["font_size"] = (int)size->value().y / 4; request.callback = [REF = m_self] { if (const auto SELF = REF.lock(); SELF) g_pHyprlock->renderOutput(SELF->outputStringPort); }; g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); } void CPasswordInputField::updateWidth() { double targetSizeX = configSize.x; if (passwordLength == 0 && placeholder.asset) targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y; targetSizeX = std::max(targetSizeX, configSize.x); if (size->goal().x != targetSizeX) { *size = Vector2D{targetSizeX, configSize.y}; size->setCallbackOnEnd([this](auto) { redrawShadow = true; pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign); }); } if (size->isBeingAnimated()) { redrawShadow = true; pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign); } } void CPasswordInputField::updateHiddenInputState() { if (!hiddenInputState.enabled || (size_t)hiddenInputState.lastPasswordLength == passwordLength) return; // randomize new thang hiddenInputState.lastPasswordLength = passwordLength; const auto BASEOK = colorConfig.hiddenBase.asOkLab(); // convert to polar coordinates const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2)); // now randomly rotate the hue const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4; // convert back to OkLab const Hyprgraphics::CColor newColor = Hyprgraphics::CColor::SOkLab{ .l = BASEOK.l, .a = OKICHCHROMA * std::cos(OKICHHUE), .b = OKICHCHROMA * std::sin(OKICHHUE), }; hiddenInputState.lastColor = {newColor, 1.0}; hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4; } void CPasswordInputField::updateColors() { const bool BORDERLESS = outThick == 0; const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock; CGradientValueData* targetGrad = nullptr; if (g_pHyprlock->m_bCapsLock && NUMLOCK && !colorConfig.both->m_bIsFallback) targetGrad = colorConfig.both; else if (g_pHyprlock->m_bCapsLock) targetGrad = colorConfig.caps; else if (NUMLOCK && !colorConfig.num->m_bIsFallback) targetGrad = colorConfig.num; if (checkWaiting) targetGrad = colorConfig.check; else if (displayFail && passwordLength == 0) targetGrad = colorConfig.fail; CGradientValueData* outerTarget = colorConfig.outer; CHyprColor innerTarget = colorConfig.inner; CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font; if (targetGrad) { if (BORDERLESS && colorConfig.swapFont) { fontTarget = targetGrad->m_vColors.front(); } else if (BORDERLESS && !colorConfig.swapFont) { innerTarget = targetGrad->m_vColors.front(); // When changing the inner color, the font cannot be fail_color fontTarget = colorConfig.font; } else if (targetGrad) { outerTarget = targetGrad; } } if (!BORDERLESS && *outerTarget != colorState.outer->goal()) *colorState.outer = *outerTarget; if (innerTarget != colorState.inner->goal()) *colorState.inner = innerTarget; colorState.font = fontTarget; } CBox CPasswordInputField::getBoundingBoxWl() const { return { Vector2D{pos.x, viewport.y - pos.y - size->value().y}, size->value(), }; } void CPasswordInputField::onHover(const Vector2D& pos) { g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT); } hyprlock-0.9.2/src/renderer/widgets/PasswordInputField.hpp000066400000000000000000000077571506744673100237540ustar00rootroot00000000000000#pragma once #include "IWidget.hpp" #include "../../defines.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "Shadowable.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../helpers/AnimatedVariable.hpp" #include #include #include #include struct SPreloadedAsset; class CPasswordInputField : public IWidget { public: CPasswordInputField() = default; virtual ~CPasswordInputField(); void registerSelf(const ASP& self); virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); virtual void onHover(const Vector2D& pos); virtual CBox getBoundingBoxWl() const; void reset(); void onFadeOutTimer(); private: AWP m_self; void updateDots(); void updateFade(); void updatePlaceholder(); void updateWidth(); void updateHiddenInputState(); void updateInputState(); void updateColors(); bool firstRender = true; bool redrawShadow = false; bool checkWaiting = false; bool displayFail = false; size_t passwordLength = 0; PHLANIMVAR size; Vector2D pos; Vector2D viewport; Vector2D configPos; Vector2D configSize; std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily; uint64_t configFailTimeoutMs = 2000; int outThick, rounding; struct { PHLANIMVAR currentAmount; bool center = false; float size = 0; float spacing = 0; int rounding = 0; std::string textFormat = ""; std::string textResourceID; ASP textAsset = nullptr; } dots; struct { PHLANIMVAR a; bool appearing = true; ASP fadeOutTimer = nullptr; bool allowFadeOut = false; } fade; struct { std::string resourceID = ""; ASP asset = nullptr; std::string currentText = ""; size_t failedAttempts = 0; std::vector registeredResourceIDs; } placeholder; struct { CHyprColor lastColor; int lastQuadrant = 0; int lastPasswordLength = 0; bool enabled = false; } hiddenInputState; struct { CGradientValueData* outer = nullptr; CHyprColor inner; CHyprColor font; CGradientValueData* fail = nullptr; CGradientValueData* check = nullptr; CGradientValueData* caps = nullptr; CGradientValueData* num = nullptr; CGradientValueData* both = nullptr; CHyprColor hiddenBase; int transitionMs = 0; bool invertNum = false; bool swapFont = false; } colorConfig; struct { PHLANIMVAR outer; PHLANIMVAR inner; // Font color is only chaned, when `swap_font_color` is set to true and no border is present. // It is not animated, because that does not look good and we would need to rerender the text for each frame. CHyprColor font; } colorState; bool fadeOnEmpty; uint64_t fadeTimeoutMs; CShadowable shadow; }; hyprlock-0.9.2/src/renderer/widgets/Shadowable.cpp000066400000000000000000000027501506744673100222160ustar00rootroot00000000000000#include "Shadowable.hpp" #include "../Renderer.hpp" #include void CShadowable::configure(AWP widget_, const std::unordered_map& props, const Vector2D& viewport_) { m_widget = widget_; viewport = viewport_; size = std::any_cast(props.at("shadow_size")); passes = std::any_cast(props.at("shadow_passes")); color = std::any_cast(props.at("shadow_color")); boostA = std::any_cast(props.at("shadow_boost")); } void CShadowable::markShadowDirty() { const auto WIDGET = m_widget.lock(); if (!m_widget) return; if (passes == 0) return; if (!shadowFB.isAllocated()) shadowFB.alloc(viewport.x, viewport.y, true); g_pRenderer->pushFb(shadowFB.m_iFb); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); ignoreDraw = true; WIDGET->draw(IWidget::SRenderData{.opacity = 1.0}); ignoreDraw = false; g_pRenderer->blurFB(shadowFB, CRenderer::SBlurParams{.size = size, .passes = passes, .colorize = color, .boostA = boostA}); g_pRenderer->popFb(); } bool CShadowable::draw(const IWidget::SRenderData& data) { if (!m_widget || passes == 0) return true; if (!shadowFB.isAllocated() || ignoreDraw) return true; CBox box = {0, 0, viewport.x, viewport.y}; g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, HYPRUTILS_TRANSFORM_NORMAL); return true; } hyprlock-0.9.2/src/renderer/widgets/Shadowable.hpp000066400000000000000000000016501506744673100222210ustar00rootroot00000000000000#pragma once #include "../Framebuffer.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "IWidget.hpp" #include #include #include class CShadowable { public: virtual ~CShadowable() = default; CShadowable() = default; void configure(AWP widget_, const std::unordered_map& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */); // instantly re-renders the shadow using the widget's draw() method void markShadowDirty(); virtual bool draw(const IWidget::SRenderData& data); private: AWP m_widget; int size = 10; int passes = 4; float boostA = 1.0; CHyprColor color{0, 0, 0, 1.0}; Vector2D viewport; // to avoid recursive shadows bool ignoreDraw = false; CFramebuffer shadowFB; }; hyprlock-0.9.2/src/renderer/widgets/Shape.cpp000066400000000000000000000104451506744673100212050ustar00rootroot00000000000000#include "Shape.hpp" #include "../Renderer.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/MiscFunctions.hpp" #include #include #include void CShape::registerSelf(const ASP& self) { m_self = self; } void CShape::configure(const std::unordered_map& props, const SP& pOutput) { viewport = pOutput->getViewport(); shadow.configure(m_self, props, viewport); try { size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport); rounding = std::any_cast(props.at("rounding")); border = std::any_cast(props.at("border_size")); color = std::any_cast(props.at("color")); borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color")); pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); angle = std::any_cast(props.at("rotate")); xray = std::any_cast(props.at("xray")); onclickCommand = std::any_cast(props.at("onclick")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CShape: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing property for CShape: {}", e.what()); // } angle = angle * M_PI / 180.0; const Vector2D VBORDER = {border, border}; const Vector2D REALSIZE = size + VBORDER * 2.0; const Vector2D OFFSET = angle == 0 ? Vector2D{0.0, 0.0} : Vector2D{1.0, 1.0}; pos = posFromHVAlign(viewport, xray ? size : REALSIZE + OFFSET * 2.0, pos, halign, valign, xray ? 0 : angle); if (xray) { shapeBox = {pos, size}; borderBox = {pos - VBORDER, REALSIZE}; } else { shapeBox = {OFFSET + VBORDER, size}; borderBox = {OFFSET, REALSIZE}; } } bool CShape::draw(const SRenderData& data) { if (firstRender) { firstRender = false; shadow.markShadowDirty(); } shadow.draw(data); const auto MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0; if (xray) { if (border > 0) { const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI)); g_pRenderer->renderBorder(borderBox, borderGrad, border, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND), data.opacity); } glEnable(GL_SCISSOR_TEST); glScissor(shapeBox.x, shapeBox.y, shapeBox.width, shapeBox.height); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); return data.opacity < 1.0; } if (!shapeFB.isAllocated()) { const int ROUND = roundingForBox(shapeBox, rounding); const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border); Debug::log(LOG, "round: {}, borderround: {}", ROUND, BORDERROUND); shapeFB.alloc(borderBox.width + (borderBox.x * 2.0), borderBox.height + (borderBox.y * 2.0), true); g_pRenderer->pushFb(shapeFB.m_iFb); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); if (border > 0) g_pRenderer->renderBorder(borderBox, borderGrad, border, BORDERROUND, 1.0); g_pRenderer->renderRect(shapeBox, color, ROUND); g_pRenderer->popFb(); } CTexture* tex = &shapeFB.m_cTex; CBox texbox = {pos, tex->m_vSize}; texbox.round(); texbox.rot = angle; g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; } CBox CShape::getBoundingBoxWl() const { return { Vector2D{pos.x, viewport.y - pos.y - size.y}, size, }; } void CShape::onClick(uint32_t button, bool down, const Vector2D& pos) { if (down && !onclickCommand.empty()) spawnAsync(onclickCommand); } void CShape::onHover(const Vector2D& pos) { if (!onclickCommand.empty()) g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER); } hyprlock-0.9.2/src/renderer/widgets/Shape.hpp000066400000000000000000000024321506744673100212070ustar00rootroot00000000000000#pragma once #include "IWidget.hpp" #include "../../helpers/Color.hpp" #include "../../config/ConfigDataValues.hpp" #include "Shadowable.hpp" #include #include #include #include class CShape : public IWidget { public: CShape() = default; virtual ~CShape() = default; void registerSelf(const ASP& self); virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); private: AWP m_self; CFramebuffer shapeFB; int rounding; double border; double angle; CHyprColor color; CGradientValueData borderGrad; Vector2D size; Vector2D pos; CBox shapeBox; CBox borderBox; bool xray; std::string halign, valign; bool firstRender = true; std::string onclickCommand; Vector2D viewport; CShadowable shadow; };