pax_global_header00006660000000000000000000000064151706511450014517gustar00rootroot0000000000000052 comment=d75e93f8ee1721d70549d96f4d14bf2948aab70c hyprwm-hyprlock-d75e93f/000077500000000000000000000000001517065114500153265ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/.clang-format000066400000000000000000000034161517065114500177050ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-hyprlock-d75e93f/.clang-tidy000066400000000000000000000072721517065114500173720ustar00rootroot00000000000000WarningsAsErrors: '*' 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 hyprwm-hyprlock-d75e93f/.github/000077500000000000000000000000001517065114500166665ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/.github/ISSUE_TEMPLATE/000077500000000000000000000000001517065114500210515ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/.github/ISSUE_TEMPLATE/bug.yml000066400000000000000000000055401517065114500223550ustar00rootroot00000000000000name: 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. hyprwm-hyprlock-d75e93f/.github/ISSUE_TEMPLATE/feature.yml000066400000000000000000000006411517065114500232300ustar00rootroot00000000000000name: 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 hyprwm-hyprlock-d75e93f/.github/workflows/000077500000000000000000000000001517065114500207235ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/.github/workflows/nix.yml000066400000000000000000000004611517065114500222450ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) uses: hyprwm/actions/.github/workflows/nix.yml@main secrets: inherit with: command: nix build --print-build-logs --keep-going hyprwm-hyprlock-d75e93f/.gitignore000066400000000000000000000002541517065114500173170ustar00rootroot00000000000000**/.cache .direnv .envrc .vscode/ CMakeCache.txt CMakeFiles/ Makefile cmake_install.cmake build/ compile_commands.json protocols/*.cpp protocols/*.hpp *.kdev4 .gdb_history hyprwm-hyprlock-d75e93f/CMakeLists.txt000066400000000000000000000120631517065114500200700ustar00rootroot00000000000000cmake_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.11.0 sdbus-c++>=2.0.0 hyprgraphics>=0.1.6) find_library(PAM_FOUND NAMES pam libpam) if(PAM_FOUND) set(PAM_LIB ${PAM_FOUND}) else() pkg_check_modules(PAM IMPORTED_TARGET pam libpam) if(PAM_FOUND) set(PAM_LIB PkgConfig::PAM) else() message(FATAL_ERROR "The required library libpam was not found.") endif() endif() message(STATUS "Found pam at ${PAM_LIB}") 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) hyprwm-hyprlock-d75e93f/LICENSE000066400000000000000000000027371517065114500163440ustar00rootroot00000000000000BSD 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. hyprwm-hyprlock-d75e93f/README.md000066400000000000000000000026621517065114500166130ustar00rootroot00000000000000# 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 - 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 ``` hyprwm-hyprlock-d75e93f/VERSION000066400000000000000000000000061517065114500163720ustar00rootroot000000000000000.9.5 hyprwm-hyprlock-d75e93f/assets/000077500000000000000000000000001517065114500166305ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/assets/example.conf000066400000000000000000000047531517065114500211430ustar00rootroot00000000000000# 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 if you wish to display a message during authentication # check_text = Authenticating... # 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 } hyprwm-hyprlock-d75e93f/flake.lock000066400000000000000000000066551517065114500172760ustar00rootroot00000000000000{ "nodes": { "hyprgraphics": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772461523, "narHash": "sha256-mI6A51do+hEUzeJKk9YSWfVHdI/SEEIBi2tp5Whq5mI=", "owner": "hyprwm", "repo": "hyprgraphics", "rev": "7d63c04b4a2dd5e59ef943b4b143f46e713df804", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprgraphics", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459629, "narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=", "owner": "hyprwm", "repo": "hyprlang", "rev": "7615ee388de18239a4ab1400946f3d0e498a8186", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459870, "narHash": "sha256-xxkK2Cvqxpf/4UGcJ/TyCwrvmiNWsKsJfFzHMp2bxis=", "owner": "hyprwm", "repo": "hyprutils", "rev": "e63f3a79334dec49f8eb1691f66f18115df04085", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459835, "narHash": "sha256-978jRz/y/9TKmZb/qD4lEYHCQGHpEXGqy+8X2lFZsak=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "0a692d4a645165eebd65f109146b8861e3a925e7", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1772433332, "narHash": "sha256-izhTDFKsg6KeVBxJS9EblGeQ8y+O8eCa6RcW874vxEc=", "owner": "NixOS", "repo": "nixpkgs", "rev": "cf59864ef8aa2e178cccedbe2c178185b0365705", "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 } hyprwm-hyprlock-d75e93f/flake.nix000066400000000000000000000035211517065114500171310ustar00rootroot00000000000000{ 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; [ hyprlock-with-deps ]; } ); 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}.nixfmt-tree); }; } hyprwm-hyprlock-d75e93f/nix/000077500000000000000000000000001517065114500161245ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/nix/default.nix000066400000000000000000000021421517065114500202670ustar00rootroot00000000000000{ lib, stdenv, cmake, pkg-config, cairo, libdrm, libGL, libxkbcommon, libgbm, hyprgraphics, hyprlang, hyprutils, hyprwayland-scanner, pam, pango, sdbus-cpp_2, 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_2 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"; }; } hyprwm-hyprlock-d75e93f/nix/overlays.nix000066400000000000000000000016461517065114500205170ustar00rootroot00000000000000{ 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-with-deps = lib.composeManyExtensions [ inputs.hyprgraphics.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default self.overlays.hyprlock ]; hyprlock = final: prev: { hyprlock = prev.callPackage ./default.nix { stdenv = prev.gcc15Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); shortRev = self.sourceInfo.shortRev or "dirty"; }; }; } hyprwm-hyprlock-d75e93f/pam/000077500000000000000000000000001517065114500161035ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/pam/hyprlock000066400000000000000000000001761517065114500176650ustar00rootroot00000000000000# PAM configuration file for hyprlock # the 'login' configuration file (see /etc/pam.d/login) auth include login hyprwm-hyprlock-d75e93f/protocols/000077500000000000000000000000001517065114500173525ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000226051517065114500252140ustar00rootroot00000000000000 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. hyprwm-hyprlock-d75e93f/src/000077500000000000000000000000001517065114500161155ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/auth/000077500000000000000000000000001517065114500170565ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/auth/Auth.cpp000066400000000000000000000066531517065114500204750ustar00rootroot00000000000000#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++; Log::logger->log(Log::INFO, "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(); } hyprwm-hyprlock-d75e93f/src/auth/Auth.hpp000066400000000000000000000040671517065114500204770ustar00rootroot00000000000000#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; hyprwm-hyprlock-d75e93f/src/auth/Fingerprint.cpp000066400000000000000000000246441517065114500220630ustar00rootroot00000000000000#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) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::WARN, "fprint: couldn't connect to Fprint service ({})", e.what()); return false; } Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::INFO, "fprint: handling status {}", result); auto matchResult = s_mapStringToTestType[result]; bool authenticated = false; bool retry = false; if (m_sDBUSState.sleeping) { stopVerify(); Log::logger->log(Log::INFO, "fprint: device suspended"); return; } switch (matchResult) { case MATCH_INVALID: Log::logger->log(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) Log::logger->log(Log::WARN, "fprint: could not claim device, {}", e->what()); else { Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::WARN, "fprint: could not start verifying, {}", e->what()); if (isRetry) m_sFailureReason = "Fingerprint auth disabled (failed to restart)"; } else { Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::WARN, "fprint: could not stop verifying, {}", e.what()); return false; } Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::WARN, "fprint: could not release device, {}", e.what()); return false; } Log::logger->log(Log::INFO, "fprint: released device"); return true; } hyprwm-hyprlock-d75e93f/src/auth/Fingerprint.hpp000066400000000000000000000032171517065114500220610ustar00rootroot00000000000000#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(); }; hyprwm-hyprlock-d75e93f/src/auth/Pam.cpp000066400000000000000000000152041517065114500203010ustar00rootroot00000000000000#include "Pam.hpp" #include "../config/ConfigManager.hpp" #include "../core/hyprlock.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #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; Log::logger->log(Log::INFO, "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: Log::logger->log(Log::ERR, "PAM: {}", msg[i]->msg); break; case PAM_TEXT_INFO: Log::logger->log(Log::INFO, "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)) { Log::logger->log(Log::ERR, R"(Pam module "/etc/pam.d/{}" does not exist! Falling back to "/etc/pam.d/su")", m_sPamModule); m_sPamModule = "su"; } m_username = getUsernameForCurrentUid(); 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; if (m_username.empty()) { m_sConversationState.failText = "Username not set"; return false; } int ret = pam_start(m_sPamModule.c_str(), m_username.c_str(), &localConv, &handle); if (ret != PAM_SUCCESS) { m_sConversationState.failText = "pam_start failed"; Log::logger->log(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"; Log::logger->log(Log::ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule); return false; } m_sConversationState.failText = "Successfully authenticated"; Log::logger->log(Log::INFO, "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) Log::logger->log(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; } hyprwm-hyprlock-d75e93f/src/auth/Pam.hpp000066400000000000000000000030161517065114500203040ustar00rootroot00000000000000#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 = ""; std::string m_username = ""; bool auth(); void resetConversation(); }; hyprwm-hyprlock-d75e93f/src/config/000077500000000000000000000000001517065114500173625ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/config/ConfigDataValues.hpp000066400000000000000000000072451517065114500232620ustar00rootroot00000000000000#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; } }; hyprwm-hyprlock-d75e93f/src/config/ConfigManager.cpp000066400000000000000000001006011517065114500225640ustar00rootroot00000000000000#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 (...) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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::expected getMainConfigPath() { static const auto PATHS = Hyprutils::Path::findConfig("hyprlock"); if (PATHS.first.has_value()) return PATHS.first.value(); else return std::unexpected{"Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr."}; } std::expected CConfigManager::resolveConfigPath(std::optional explicitPath) { std::string configPath = ""; if (explicitPath.has_value()) { configPath = explicitPath.value(); configPath = absolutePath(configPath, ""); } else { auto mainConfigPath = getMainConfigPath(); if (!mainConfigPath.has_value()) return std::unexpected{mainConfigPath.error()}; configPath = mainConfigPath.value(); } if (!std::filesystem::exists(configPath)) return std::unexpected{std::vformat("No config file at \"{}\"", std::make_format_args(configPath))}; return configPath; } CConfigManager::CConfigManager(const char* configPath) : m_configCurrentPath(configPath), m_config(configPath, Hyprlang::SConfigOptions{ .throwAllErrors = true, .allowMissingConfig = false, }) { ; } 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", "check_text", Hyprlang::STRING{""}); 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) Log::logger->log(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())}, {"check_text", m_config.getSpecialConfigValue("input-field", "check_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) { Log::logger->log(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(m_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"); Log::logger->log(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 == m_configCurrentPath) { Log::logger->log(Log::WARN, "source= skipping invalid path"); continue; } if (!std::filesystem::is_regular_file(PATH)) { if (std::filesystem::exists(PATH)) { Log::logger->log(Log::WARN, "source= skipping non-file {}", PATH); continue; } Log::logger->log(Log::ERR, "source= file doesnt exist"); return "source file " + PATH + " doesn't exist!"; } // allow for nested config parsing auto backupConfigPath = m_configCurrentPath; m_configCurrentPath = PATH; m_config.parseFile(PATH.c_str()); m_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 {}; } hyprwm-hyprlock-d75e93f/src/config/ConfigManager.hpp000066400000000000000000000026161517065114500226000ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "../defines.hpp" class CConfigManager { public: static std::expected resolveConfigPath(std::optional explicitPath); CConfigManager(const char* 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 m_configCurrentPath; Hyprutils::Animation::CAnimationConfigTree m_AnimationTree; private: Hyprlang::CConfig m_config; }; inline UP g_pConfigManager; hyprwm-hyprlock-d75e93f/src/core/000077500000000000000000000000001517065114500170455ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/core/AnimationManager.cpp000066400000000000000000000115151517065114500227660ustar00rootroot00000000000000#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"); const auto CPY = m_vActiveAnimatedVariables; for (const auto& PAV : CPY) { 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; } hyprwm-hyprlock-d75e93f/src/core/AnimationManager.hpp000066400000000000000000000020261517065114500227700ustar00rootroot00000000000000#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; pav = makeUnique>(); pav->create2(EAVTYPE, static_cast(this), pav, v); pav->setConfig(pConfig); } bool m_bTickScheduled = false; }; inline UP g_pAnimationManager; hyprwm-hyprlock-d75e93f/src/core/CursorShape.cpp000066400000000000000000000013321517065114500220060ustar00rootroot00000000000000#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); } hyprwm-hyprlock-d75e93f/src/core/CursorShape.hpp000066400000000000000000000006601517065114500220160ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/core/Egl.cpp000066400000000000000000000114551517065114500202660ustar00rootroot00000000000000#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) { Log::logger->log(Log::CRIT, "Failed to create EGL display"); goto error; } if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) { Log::logger->log(Log::CRIT, "Failed to initialize EGL"); goto error; } if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) { Log::logger->log(Log::CRIT, "eglChooseConfig failed"); goto error; } if (matched == 0) { Log::logger->log(Log::CRIT, "Failed to match an EGL config"); goto error; } eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs); if (eglContext == EGL_NO_CONTEXT) { Log::logger->log(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(); } static const char* eglErrorToString(EGLint error) { switch (error) { case EGL_SUCCESS: return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; } return "Unknown"; } EGLSurface CEGL::createPlatformWindowSurfaceEXT(wl_egl_window* eglWindow) { EGLSurface eglSurface = eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr); if (eglSurface == EGL_NO_SURFACE) Log::logger->log(Log::ERR, "Failed to allocate egl window surface, error: {}", eglErrorToString(eglGetError())); return eglSurface; } void CEGL::makeCurrent(EGLSurface surf) { if (eglMakeCurrent(eglDisplay, surf, surf, eglContext) == EGL_FALSE) Log::logger->log(Log::ERR, "Failed to eglMakeCurrent, error: {}", eglErrorToString(eglGetError())); } bool CEGL::swapBuffers(EGLSurface surf) { if (eglSwapBuffers(eglDisplay, surf) == EGL_FALSE) { Log::logger->log(Log::ERR, "Failed to eglSwapBuffers, error: {}", eglErrorToString(eglGetError())); return false; } return true; } hyprwm-hyprlock-d75e93f/src/core/Egl.hpp000066400000000000000000000010741517065114500202670ustar00rootroot00000000000000#pragma once #include #include #include #include "../defines.hpp" class CEGL { public: CEGL(wl_display*); ~CEGL(); EGLDisplay eglDisplay; EGLConfig eglConfig; EGLContext eglContext; EGLSurface createPlatformWindowSurfaceEXT(wl_egl_window* eglWindow); void makeCurrent(EGLSurface surf); bool swapBuffers(EGLSurface surf); bool m_isNvidia = false; private: PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT; }; inline UP g_pEGL; hyprwm-hyprlock-d75e93f/src/core/LockSurface.cpp000066400000000000000000000133631517065114500217600ustar00rootroot00000000000000#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; Log::logger->log(Log::INFO, "Got fractional scale: {:.1f}%", fractionalScale * 100.F); if (!SAMESCALE && readyForFrame) onScaleUpdate(); }); viewport = makeShared(PVIEWPORTER->sendGetViewport(surface->resource())); } if (!PFRACTIONALMGR) Log::logger->log(Log::INFO, "No fractional-scale support! Oops, won't be able to scale!"); if (!PVIEWPORTER) Log::logger->log(Log::INFO, "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_) { Log::logger->log(Log::INFO, "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); Log::logger->log(Log::INFO, "Configuring surface for logical {} and pixel {}", logicalSize, size); surface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF); if (eglWindow && eglSurface) { Log::logger->log(Log::INFO, "Resizing existing eglWindow"); wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0); } else { if (eglWindow) wl_egl_window_destroy(eglWindow); eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y); if (!eglWindow) { // Only fails when unable to allocate the wl_egl_window structure or size x or y is <= 0. Log::logger->log(Log::CRIT, "Failed to create wayland egl window"); readyForFrame = false; return; } if (eglSurface) eglDestroySurface(g_pEGL->eglDisplay, eglSurface); eglSurface = g_pEGL->createPlatformWindowSurfaceEXT(eglWindow); if (eglSurface == EGL_NO_SURFACE) { readyForFrame = false; return; } } if (readyForFrame && !(SAMESIZE && SAMESCALE)) { Log::logger->log(Log::INFO, "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 (Log::logger->verbose()) { const auto POUTPUT = m_outputRef.lock(); Log::logger->log(Log::TRACE, "[{}] frame {}, Current fps: {:.2f}", POUTPUT->stringPort, m_frames, 1000.f / (frameTime - m_lastFrameTime)); } m_lastFrameTime = frameTime; m_frames++; onCallback(); }); if (!g_pEGL->swapBuffers(eglSurface)) { frameCallback.reset(); needsFrame = true; return; } 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; } hyprwm-hyprlock-d75e93f/src/core/LockSurface.hpp000066400000000000000000000031361517065114500217620ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/core/Output.cpp000066400000000000000000000046471517065114500210640ustar00rootroot00000000000000#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} : ""; Log::logger->log(Log::INFO, "output {} description {}", m_ID, stringDesc); }); m_wlOutput->setName([this](CCWlOutput* r, const char* name) { stringName = std::string{name} + stringName; stringPort = std::string{name}; Log::logger->log(Log::INFO, "output {} name {}", name, name); }); m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; }); m_wlOutput->setDone([this](CCWlOutput* r) { done = true; Log::logger->log(Log::INFO, "output {} done", m_ID); if (g_pHyprlock->m_lockAquired && !m_sessionLockSurface) { Log::logger->log(Log::INFO, "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_; Log::logger->log(Log::INFO, "output {} make {} model {}", m_ID, make ? make : "", model ? model : ""); }); } void COutput::createSessionLockSurface() { if (!m_self.valid()) { Log::logger->log(Log::ERR, "output {} dead??", m_ID); return; } if (m_sessionLockSurface) { Log::logger->log(Log::ERR, "output {} already has a session lock surface", m_ID); return; } if (size == Vector2D{0, 0}) { Log::logger->log(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; } hyprwm-hyprlock-d75e93f/src/core/Output.hpp000066400000000000000000000016461517065114500210650ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/core/Seat.cpp000066400000000000000000000166301517065114500204530ustar00rootroot00000000000000#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) Log::logger->log(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) { Log::logger->log(Log::INFO, "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) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(Log::ERR, "Failed to compile xkb keymap"); return; } m_pXKBState = xkb_state_new(m_pXKBKeymap); if (!m_pXKBState) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(Log::INFO, "Exposed seat name: {}", name ? name : "nullptr"); }); } void CSeatManager::registerCursorShape(SP shape) { m_pCursorShape = makeUnique(shape); } bool CSeatManager::registered() { return m_pSeat; } hyprwm-hyprlock-d75e93f/src/core/Seat.hpp000066400000000000000000000015661517065114500204620ustar00rootroot00000000000000#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(); hyprwm-hyprlock-d75e93f/src/core/Timer.cpp000066400000000000000000000013401517065114500206270ustar00rootroot00000000000000#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; } hyprwm-hyprlock-d75e93f/src/core/Timer.hpp000066400000000000000000000013651517065114500206430ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/core/hyprlock.cpp000066400000000000000000000772101517065114500214130ustar00rootroot00000000000000#include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" #include "../renderer/AsyncResourceManager.hpp" #include "../auth/Auth.hpp" #include "../auth/Fingerprint.hpp" #include "./Egl.hpp" #include "./Seat.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 } static bool screencopyRequired() { 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)); if (FADENEEDSSC) return true; 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"; }); return BGSCREENSHOT; } CHyprlock::CHyprlock(std::string_view wlDisplay, const bool immediateRender, const int graceSeconds) : m_screencopyRequired(screencopyRequired()) { setMallocThreshold(); m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : std::string{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) { Log::logger->log(Log::INFO, "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) { Log::logger->log(Log::ERR, "[core] Couldn't find a render node"); return nullptr; } Log::logger->log(Log::TRACE, "[core] createGBMDevice: render node {}", renderNode); int fd = open(renderNode, O_RDWR | O_CLOEXEC); if (fd < 0) { Log::logger->log(Log::ERR, "[core] couldn't open render node"); free(renderNode); return nullptr; } free(renderNode); return gbm_create_device(fd); } void CHyprlock::addDmabufListener() { if (!dma.linuxDmabuf || !dma.linuxDmabufFeedback) return; dma.linuxDmabufFeedback->setTrancheDone([this](CCZwpLinuxDmabufFeedbackV1* r) { Log::logger->log(Log::TRACE, "[core] dmabufFeedbackTrancheDone"); dma.deviceUsed = false; }); dma.linuxDmabufFeedback->setTrancheFormats([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* indices) { Log::logger->log(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; Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(Log::TRACE, "[core] dmabufFeedbackFormatTable"); dma.dmabufMods.clear(); dma.formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (dma.formatTable == MAP_FAILED) { Log::logger->log(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) { Log::logger->log(Log::INFO, "[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; Log::logger->log(Log::INFO, " | got iface: {} v{}", IFACE, version); if (IFACE == zwp_linux_dmabuf_v1_interface.name) { if (!m_screencopyRequired) return; if (version < 4) { Log::logger->log(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()) { Log::logger->log(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; Log::logger->log(Log::INFO, " > Bound to {} v{}", IFACE, version); }); m_sWaylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) { Log::logger->log(Log::INFO, " | 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); } if (m_vOutputs.empty()) eglReleaseThread(); }); wl_display_roundtrip(m_sWaylandState.display); if (!m_sWaylandState.sessionLock) { Log::logger->log(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_asyncResourceManager = makeUnique(); g_pAuth = makeUnique(); g_pAuth->start(); Log::logger->log(Log::INFO, "Running on {}", m_sCurrentDesktop); g_asyncResourceManager->enqueueStaticAssets(); if (m_screencopyRequired) g_asyncResourceManager->enqueueScreencopyFrames(); else Log::logger->log(Log::INFO, "Skipping screencopy"); 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) g_asyncResourceManager->gatherInitialResources(m_sWaylandState.display); // 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) { Log::logger->log(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); Log::logger->log(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()) { ; } } processTimers(); } const auto DPY = m_sWaylandState.display; // Wake threads so they observe m_bTerminate and exit; pending timers are dropped m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); pthread_kill(pollThr.native_handle(), SIGRTMIN); g_pAuth->terminate(); // Wait for threads to exit before destroying globals to prevent // use-after-free in timer callbacks referencing g_asyncResourceManager pollThr.join(); timersThr.join(); // Now safe to destroy globals — no more timer callbacks can fire m_sWaylandState = {}; dma = {}; m_vOutputs.clear(); g_pSeatManager.reset(); g_asyncResourceManager.reset(); g_pRenderer.reset(); g_pEGL.reset(); wl_display_disconnect(DPY); Log::logger->log(Log::INFO, "Reached the end, exiting"); } void CHyprlock::unlock() { if (!m_bLocked) { Log::logger->log(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()) { Log::logger->log(Log::ERR, "Invalid key down event (key already pressed?)"); return; } else if (!down && std::ranges::find(m_vPressedKeys, key) == m_vPressedKeys.end()) { Log::logger->log(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))) { Log::logger->log(Log::INFO, "Clearing password buffer"); m_sPasswordState.passBuffer = ""; } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { Log::logger->log(Log::INFO, "Authenticating"); static const auto IGNOREEMPTY = g_pConfigManager->getValue("general:ignore_empty_input"); if (m_sPasswordState.passBuffer.empty() && *IGNOREEMPTY) { Log::logger->log(Log::INFO, "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() { Log::logger->log(Log::INFO, "Locking session"); m_sLockState.lock = makeShared(m_sWaylandState.sessionLock->sendLock()); if (!m_sLockState.lock) { Log::logger->log(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 existing outputs for (auto& o : m_vOutputs) { if (!o->done) continue; o->createSessionLockSurface(); } return true; } void CHyprlock::releaseSessionLock() { Log::logger->log(Log::INFO, "Unlocking session"); if (m_bTerminate) { Log::logger->log(Log::ERR, "Unlock already happend?"); return; } if (!m_sLockState.lock) { Log::logger->log(Log::ERR, "Unlock without a lock object!"); return; } if (!m_bLocked) { // Would be a protocol error to allow this Log::logger->log(Log::ERR, "Trying to unlock the session, but never recieved the locked event!"); return; } m_sLockState.lock->sendUnlockAndDestroy(); m_sLockState.lock = nullptr; Log::logger->log(Log::INFO, "Unlocked, exiting!"); m_bTerminate = true; m_bLocked = false; wl_display_roundtrip(m_sWaylandState.display); } void CHyprlock::onLockLocked() { Log::logger->log(Log::INFO, "onLockLocked called"); m_bLocked = true; } void CHyprlock::onLockFinished() { Log::logger->log(Log::INFO, "onLockFinished called. Seems we got yeeten. Is another lockscreen running?"); if (!m_sLockState.lock) { Log::logger->log(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; } void CHyprlock::processTimers() { // 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(); } 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; } hyprwm-hyprlock-d75e93f/src/core/hyprlock.hpp000066400000000000000000000134251517065114500214160ustar00rootroot00000000000000#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 "Timer.hpp" #include #include #include #include #include #include #include #include struct SDMABUFModifier { uint32_t fourcc = 0; uint64_t mod = 0; }; class CHyprlock { public: CHyprlock(std::string_view 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 processTimers(); 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; bool m_screencopyRequired = 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; hyprwm-hyprlock-d75e93f/src/defines.hpp000066400000000000000000000007411517065114500202450ustar00rootroot00000000000000#pragma once #include #include #include #include using namespace Hyprutils::Memory; using namespace Hyprgraphics; using ResourceID = size_t; #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer #define ASP CAtomicSharedPointer #define AWP CAtomicWeakPointer typedef int64_t OUTPUTID; constexpr OUTPUTID OUTPUT_INVALID = -1; hyprwm-hyprlock-d75e93f/src/helpers/000077500000000000000000000000001517065114500175575ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/helpers/AnimatedVariable.hpp000066400000000000000000000035651517065114500234710ustar00rootroot00000000000000#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 = UP>; template using PHLANIMVARREF = WP>; hyprwm-hyprlock-d75e93f/src/helpers/Color.cpp000066400000000000000000000026511517065114500213450ustar00rootroot00000000000000#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}; } hyprwm-hyprlock-d75e93f/src/helpers/Color.hpp000066400000000000000000000023211517065114500213440ustar00rootroot00000000000000#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 }; hyprwm-hyprlock-d75e93f/src/helpers/Log.hpp000066400000000000000000000050561517065114500210170ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include #include #include #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ Log::logger->log(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 Log { class CLogger { public: template void log(Hyprutils::CLI::eLogLevel level, std::format_string fmt, Args&&... args) { if (m_quiet) return; if (level == Hyprutils::CLI::LOG_TRACE && !m_verbose) return; m_logger.log(level, std::vformat(fmt.get(), std::make_format_args(args...))); } void setVerbose() { m_verbose = true; m_logger.setLogLevel(Hyprutils::CLI::LOG_TRACE); } void setQuiet() { m_quiet = true; } bool verbose() { return m_verbose; } private: bool m_quiet = false; bool m_verbose = false; Hyprutils::CLI::CLogger m_logger; }; inline UP logger = makeUnique(); // inline constexpr const Hyprutils::CLI::eLogLevel WARN = Hyprutils::CLI::LOG_WARN; inline constexpr const Hyprutils::CLI::eLogLevel ERR = Hyprutils::CLI::LOG_ERR; inline constexpr const Hyprutils::CLI::eLogLevel CRIT = Hyprutils::CLI::LOG_CRIT; inline constexpr const Hyprutils::CLI::eLogLevel INFO = Hyprutils::CLI::LOG_DEBUG; inline constexpr const Hyprutils::CLI::eLogLevel TRACE = Hyprutils::CLI::LOG_TRACE; }; hyprwm-hyprlock-d75e93f/src/helpers/Math.cpp000066400000000000000000000024161517065114500211570ustar00rootroot00000000000000#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; } hyprwm-hyprlock-d75e93f/src/helpers/Math.hpp000066400000000000000000000004741517065114500211660ustar00rootroot00000000000000#pragma once #include #include #include #include using namespace Hyprutils::Math; eTransform wlTransformToHyprutils(wl_output_transform t); wl_output_transform invertTransform(wl_output_transform tr); hyprwm-hyprlock-d75e93f/src/helpers/MiscFunctions.cpp000066400000000000000000000146371517065114500230620ustar00rootroot00000000000000#include "MiscFunctions.hpp" #include "Log.hpp" #include #include #include #include #include #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) { Log::logger->log(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) { Log::logger->log(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); Log::logger->log(Log::CRIT, "createPoolFile: fcntl < 0"); return -1; } if (ftruncate(FD, size) < 0) { close(FD); Log::logger->log(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()) { Log::logger->log(Log::ERR, "Failed to run \"{}\"", cmd); return ""; } if (!proc.stdErr().empty()) Log::logger->log(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()) Log::logger->log(Log::ERR, "Failed to start \"{}\"", cmd); } std::string getUsernameForCurrentUid() { const uid_t UID = getuid(); auto uidPassword = getpwuid(UID); if (!uidPassword || !uidPassword->pw_name) { Log::logger->log(Log::ERR, "Failed to get username for uid {} (getpwuid)", UID); return ""; } return std::string{uidPassword->pw_name}; } hyprwm-hyprlock-d75e93f/src/helpers/MiscFunctions.hpp000066400000000000000000000006361517065114500230610ustar00rootroot00000000000000#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); std::string getUsernameForCurrentUid(); hyprwm-hyprlock-d75e93f/src/main.cpp000066400000000000000000000072711517065114500175540ustar00rootroot00000000000000 #include "config/ConfigManager.hpp" #include "core/hyprlock.hpp" #include "helpers/Log.hpp" #include "core/AnimationManager.hpp" #include #include 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::vector args(argv, argv + argc); Hyprutils::CLI::CArgumentParser argParser(args); ASSERT(argParser.registerBoolOption("help", "h", "Show this help message").has_value()); ASSERT(argParser.registerBoolOption("version", "V", "Print hyprlock version, then exit").has_value()); ASSERT(argParser.registerBoolOption("verbose", "v", "Enable verbose logging").has_value()); ASSERT(argParser.registerBoolOption("quiet", "q", "Disable logging").has_value()); ASSERT(argParser.registerStringOption("config", "c", "Specify config file to use").has_value()); ASSERT(argParser.registerIntOption("grace", "g", "Seconds before authentication is required").has_value()); ASSERT(argParser.registerBoolOption("immediate-render", "", "Draw background immediately (Don't wait for resources)").has_value()); ASSERT(argParser.registerBoolOption("no-fade-in", "", "Disable the fade-in animation").has_value()); ASSERT(argParser.registerStringOption("display", "", "Specify the Wayland display to connect to").has_value()); ASSERT(argParser.registerIntOption("immediate", "", "[Deprecated] (Use \"--grace 0\" instead)").has_value()); auto options = argParser.parse(); if (!options.has_value()) { Log::logger->log(Log::ERR, "Invalid argument: {}", options.error()); return 1; } if (argParser.getBool("help")) { std::print("{}", argParser.getDescription("Hyprlock CLI Arguments", 87)); return 0; } if (argParser.getBool("version")) { printVersion(); return 0; } if (argParser.getBool("verbose")) Log::logger->setVerbose(); bool quiet = argParser.getBool("quiet").value_or(false); if (quiet) Log::logger->setQuiet(); int graceSeconds = argParser.getInt("grace").value_or(0); if (argParser.getBool("immediate")) { graceSeconds = 0; Log::logger->log(Log::WARN, R"("--immediate" is deprecated. Use the "--grace" option instead.)"); } bool immediateRender = argParser.getBool("immediate-render").value_or(false); bool noFadeIn = argParser.getBool("no-fade-in").value_or(false); if (!quiet) printVersion(); g_pAnimationManager = makeUnique(); auto configPath = CConfigManager::resolveConfigPath(argParser.getString("config")); if (!configPath.has_value()) { Log::logger->log(Log::CRIT, " Config path error: {}", configPath.error()); return 1; } try { g_pConfigManager = makeUnique(configPath.value().c_str()); g_pConfigManager->init(); } catch (const std::exception& ex) { Log::logger->log(Log::CRIT, "Config threw: {}", ex.what()); return 1; } if (noFadeIn) g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default"); try { g_pHyprlock = makeUnique(argParser.getString("display").value_or(""), immediateRender, graceSeconds); g_pHyprlock->run(); } catch (const std::exception& ex) { Log::logger->log(Log::CRIT, "Hyprlock threw: {}", ex.what()); return 1; } return 0; } hyprwm-hyprlock-d75e93f/src/renderer/000077500000000000000000000000001517065114500177235ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/renderer/AsyncResourceManager.cpp000066400000000000000000000315461517065114500245200ustar00rootroot00000000000000#include "AsyncResourceManager.hpp" #include "./resources/TextCmdResource.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include "../core/hyprlock.hpp" #include "../config/ConfigManager.hpp" #include "../core/Egl.hpp" #include #include #include #include #include using namespace Hyprgraphics; using namespace Hyprutils::OS; static inline ResourceID scopeResourceID(uint8_t scope, size_t in) { return (in & ~0x0f) | scope; } ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource::STextResourceData& s) { // TODO: Currently ignores the font string and resulting distribution is probably not perfect. const auto H1 = std::hash{}(s.text); const auto H2 = std::hash{}(s.color.asRgb().r); const auto H3 = std::hash{}(s.color.asRgb().g) + s.fontSize; const auto H4 = std::hash{}(s.color.asRgb().b) + s.align; return scopeResourceID(1, H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3)); } ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision) { return scopeResourceID(2, resourceIDForTextRequest(s) ^ (revision << 32)); } ResourceID CAsyncResourceManager::resourceIDForImageRequest(const std::string& path, size_t revision) { return scopeResourceID(3, std::hash{}(path) ^ (revision << 32)); } ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& port) { return scopeResourceID(4, std::hash{}(port)); } ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { const auto RESOURCEID = resourceIDForTextRequest(params); if (request(RESOURCEID, widget)) { Log::logger->log(Log::TRACE, "Reusing text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; Log::logger->log(Log::TRACE, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForTextCmdRequest(params, revision); if (request(RESOURCEID, widget)) { Log::logger->log(Log::TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; Log::logger->log(Log::TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForImageRequest(path, revision); if (request(RESOURCEID, widget)) { Log::logger->log(Log::TRACE, "Reusing image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; Log::logger->log(Log::TRACE, "Requesting image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } ASP CAsyncResourceManager::getAssetByID(size_t id) { if (!m_assets.contains(id)) return nullptr; return m_assets[id].texture; } void CAsyncResourceManager::enqueueStaticAssets() { const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); for (auto& c : CWIDGETS) { if (c.type == "background" || c.type == "image") { std::string path = std::any_cast(c.values.at("path")); if (path.empty() || path == "screenshot") continue; requestImage(path, 0, nullptr); } } } void CAsyncResourceManager::enqueueScreencopyFrames() { if (g_pHyprlock->m_vOutputs.empty()) return; for (const auto& MON : g_pHyprlock->m_vOutputs) { m_scFrames.emplace_back(makeUnique()); auto* frame = m_scFrames.back().get(); frame->capture(MON); m_assets.emplace(frame->m_resourceID, SPreloadedTexture{.texture = nullptr, .refs = 1}); } } void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { if (!scFrame.m_ready || !m_assets.contains(scFrame.m_resourceID)) { Log::logger->log(Log::ERR, "Bogus call to CAsyncResourceManager::screencopyToTexture. This is a bug!"); return; } m_assets[scFrame.m_resourceID].texture = scFrame.m_asset; Log::logger->log(Log::TRACE, "Done sc frame {}", scFrame.m_resourceID); std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); if (m_scFrames.empty()) { Log::logger->log(Log::INFO, "Gathered all screencopy frames - removing dmabuf listeners"); g_pHyprlock->removeDmabufListener(); } } void CAsyncResourceManager::gatherInitialResources(wl_display* display) { const auto MAXDELAYMS = 2000; // 2 Seconds const auto STARTGATHERTP = std::chrono::system_clock::now(); m_gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)}; int fdcount = 1; pollfd pollfds[2]; pollfds[0] = { .fd = wl_display_get_fd(display), .events = POLLIN, }; if (m_gatheredEventfd.isValid()) { pollfds[1] = { .fd = m_gatheredEventfd.get(), .events = POLLIN, }; fdcount++; } bool gathered = false; while (!gathered) { wl_display_flush(display); if (wl_display_prepare_read(display) == 0) { if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) { RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); wl_display_cancel_read(display); continue; } wl_display_read_events(display); wl_display_dispatch_pending(display); } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); wl_display_dispatch(display); } g_pHyprlock->processTimers(); if (std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) { Log::logger->log(Log::WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS); break; } gathered = m_resources.empty() && m_scFrames.empty(); } Log::logger->log(Log::INFO, "Resources gathered after {} milliseconds", std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count()); } bool CAsyncResourceManager::checkIdPresent(ResourceID id) { return m_assets.contains(id); } void CAsyncResourceManager::unload(ASP texture) { auto preload = std::ranges::find_if(m_assets, [texture](const auto& a) { return a.second.texture == texture; }); if (preload == m_assets.end()) return; preload->second.refs--; if (preload->second.refs == 0) { Log::logger->log(Log::TRACE, "Releasing resourceID: {}!", preload->first); m_assets.erase(preload->first); } } void CAsyncResourceManager::unloadById(ResourceID id) { if (!m_assets.contains(id)) return; m_assets[id].refs--; if (m_assets[id].refs == 0) { Log::logger->log(Log::TRACE, "Releasing resourceID: {}!", id); m_assets.erase(id); } } bool CAsyncResourceManager::request(ResourceID id, const AWP& widget) { if (!m_assets.contains(id)) { // New asset!! m_assets.emplace(id, SPreloadedTexture{.texture = nullptr, .refs = 1}); return false; } m_assets[id].refs++; if (m_assets[id].texture) { // Asset already present. Dispatch the asset callback function. const auto& TEXTURE = m_assets[id].texture; if (auto w = widget.lock()) { w->onAssetUpdate(id, TEXTURE); // TODO: add a centalized mechanism to render in one place in the event loop to avoid duplicate render calls g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->renderAllOutputs(); }, nullptr); } } else if (widget) { // Asset currently in-flight. Add the widget reference to in order for the callback to get dispatched later. m_resourcesMutex.lock(); if (!m_resources.contains(id)) { Log::logger->log(Log::ERR, "In-flight resourceID: {} not found! This is a bug.", id); m_resourcesMutex.unlock(); return true; } m_resources[id].second.emplace_back(widget); m_resourcesMutex.unlock(); } return true; } void CAsyncResourceManager::enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget) { m_gatherer.enqueue(resource); m_resourcesMutex.lock(); if (m_resources.contains(resourceID)) Log::logger->log(Log::ERR, "Resource already enqueued! This is a bug."); m_resources[resourceID] = {resource, {widget}}; m_resourcesMutex.unlock(); resource->m_events.finished.listenStatic([resourceID]() { if (!g_pHyprlock) return; g_pHyprlock->addTimer( std::chrono::milliseconds(0), [](auto, void* resourceID) { if (g_asyncResourceManager) g_asyncResourceManager->onResourceFinished((size_t)resourceID); }, (void*)resourceID); }); } void CAsyncResourceManager::onResourceFinished(ResourceID id) { m_resourcesMutex.lock(); if (!m_resources.contains(id)) { m_resourcesMutex.unlock(); return; } const auto RESOURCE = m_resources[id].first; const auto WIDGETS = m_resources[id].second; m_resources.erase(id); m_resourcesMutex.unlock(); if (!m_assets.contains(id) || m_assets[id].refs == 0) // Not referenced? Drop it return; if (!RESOURCE || !RESOURCE->m_asset.cairoSurface) return; Log::logger->log(Log::TRACE, "Resource to texture id:{}", id); g_pEGL->makeCurrent(nullptr); const auto texture = makeAtomicShared(); const cairo_status_t SURFACESTATUS = (cairo_status_t)RESOURCE->m_asset.cairoSurface->status(); const auto CAIROFORMAT = cairo_image_surface_get_format(RESOURCE->m_asset.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) { Log::logger->log(Log::ERR, "resourceID: {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS)); texture->m_iType = TEXTURE_INVALID; } texture->m_vSize = RESOURCE->m_asset.pixelSize; texture->allocate(); glBindTexture(GL_TEXTURE_2D, 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, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data()); m_assets[id].texture = texture; for (const auto& widget : WIDGETS) { if (auto w = widget.lock()) w->onAssetUpdate(id, texture); } g_pHyprlock->renderAllOutputs(); if (!m_gathered && !g_pHyprlock->m_bImmediateRender) { m_resourcesMutex.lock(); if (m_resources.empty()) { m_gathered = true; if (m_gatheredEventfd.isValid()) eventfd_write(m_gatheredEventfd.get(), 1); m_gatheredEventfd.reset(); } m_resourcesMutex.unlock(); } } hyprwm-hyprlock-d75e93f/src/renderer/AsyncResourceManager.hpp000066400000000000000000000106531517065114500245210ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "./Texture.hpp" #include "./Screencopy.hpp" #include "./widgets/IWidget.hpp" #include #include #include #include #include class CAsyncResourceManager { public: // Notes on resource lifetimes: // Resources id's are the result of hashing the requested resource parameters. // When a new request is made, adding a new entry to the m_assets map is done immediatly. // Subsequent calls through this section with the same resource id will increment the texture's references. // The manager will release the resource when refs reaches 0, while the resource itelf may outlife it's reference in the manager. // Why not use ASP/AWP for this? // The problem is that we want to to increment the reference as soon as requesting the resource id. // Not only when actually retrieving the asset with `getAssetById`. // // Improvement idea: Make a wrapper object that is returned when requesting and contains the resource id. Then we can unload with RAII. // Those are hash functions that return the id for a requested resource. static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s); // Consumer needs to increment the revision parameter to get a new command evaluation. static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision); // Image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id. static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision); static ResourceID resourceIDForScreencopy(const std::string& port); struct SPreloadedTexture { ASP texture; size_t refs = 0; }; CAsyncResourceManager() = default; ~CAsyncResourceManager() = default; ResourceID requestText(const CTextResource::STextResourceData& params, const AWP& widget); // Same as requestText but substitute the text with what launching sh -c request.text returns. ResourceID requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP& widget); ResourceID requestImage(const std::string& path, size_t revision, const AWP& widget); ASP getAssetByID(ResourceID id); void unload(ASP resource); void unloadById(ResourceID id); void enqueueStaticAssets(); void enqueueScreencopyFrames(); void screencopyToTexture(const CScreencopyFrame& scFrame); void gatherInitialResources(wl_display* display); bool checkIdPresent(ResourceID id); private: // Returns whether or not the id was already requested. // Makes sure the widgets onAssetCallback function gets called. bool request(ResourceID id, const AWP& widget); // Adds a new resource to m_resources and passes it to m_gatherer. void enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget); // Callback for finished resources. // Copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map. // Removes the entry in m_resources. // Call onAssetUpdate for all stored widget references. void onResourceFinished(ResourceID id); // For polling when using gatherInitialResources. bool m_gathered = false; Hyprutils::OS::CFileDescriptor m_gatheredEventfd; bool m_exit = false; int m_loadedAssets = 0; // not shared between threads std::unordered_map m_assets; std::vector> m_scFrames; // shared between threads std::mutex m_resourcesMutex; std::unordered_map, std::vector>>> m_resources; Hyprgraphics::CAsyncResourceGatherer m_gatherer; }; inline UP g_asyncResourceManager; hyprwm-hyprlock-d75e93f/src/renderer/Framebuffer.cpp000066400000000000000000000102071517065114500226530ustar00rootroot00000000000000#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) { Log::logger->log(Log::ERR, "Framebuffer incomplete, couldn't create! (FB status: {})", status); abort(); } Log::logger->log(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) { Log::logger->log(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; } hyprwm-hyprlock-d75e93f/src/renderer/Framebuffer.hpp000066400000000000000000000011241517065114500226560ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/renderer/Renderer.cpp000066400000000000000000000626651517065114500222140ustar00rootroot00000000000000#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 && !Log::logger->verbose()) return; Log::logger->log(Log::INFO, "[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; // render widgets const auto WIDGETS = getOrCreateWidgetsFor(surf); for (auto& w : WIDGETS) { feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame; } 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 { Log::logger->log(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() { Log::logger->log(Log::INFO, "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); } hyprwm-hyprlock-d75e93f/src/renderer/Renderer.hpp000066400000000000000000000051321517065114500222030ustar00rootroot00000000000000#pragma once #include #include #include "Shader.hpp" #include "../defines.hpp" #include "../core/LockSurface.hpp" #include "../helpers/AnimatedVariable.hpp" #include "../helpers/Color.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; hyprwm-hyprlock-d75e93f/src/renderer/Screencopy.cpp000066400000000000000000000453401517065114500225470ustar00rootroot00000000000000#include "Screencopy.hpp" #include "./AsyncResourceManager.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; // void CScreencopyFrame::capture(SP pOutput) { RASSERT(pOutput, "Screencopy, but no valid output"); static const auto SCMODE = g_pConfigManager->getValue("general:screencopy_mode"); m_asset = makeAtomicShared(); m_resourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort); m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, pOutput->m_wlOutput->resource())); if (*SCMODE == 1) m_frame = makeUnique(m_sc); else m_frame = makeUnique(m_sc); m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) { Log::logger->log(Log::TRACE, "[sc] wlrOnBufferDone for {}", (void*)this); if (!m_frame || !m_frame->onBufferDone() || !m_frame->m_wlBuffer) { Log::logger->log(Log::ERR, "[sc] Failed to create a wayland buffer for the screencopy frame"); return; } m_sc->sendCopy(m_frame->m_wlBuffer->resource()); Log::logger->log(Log::TRACE, "[sc] wlr frame copied"); }); m_sc->setFailed([this](CCZwlrScreencopyFrameV1* r) { Log::logger->log(Log::ERR, "[sc] wlrOnFailed for {}", (void*)r); m_frame.reset(); }); m_sc->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) { Log::logger->log(Log::TRACE, "[sc] wlrOnReady for {}", (void*)this); if (!m_frame || !m_frame->onBufferReady(m_asset)) { Log::logger->log(Log::ERR, "[sc] Failed to bind the screencopy buffer to a texture"); return; } m_sc.reset(); m_ready = true; g_asyncResourceManager->screencopyToTexture(*this); }); } CSCDMAFrame::CSCDMAFrame(SP sc) : m_sc(sc) { if (!glEGLImageTargetTexture2DOES) { glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (!glEGLImageTargetTexture2DOES) { Log::logger->log(Log::ERR, "[sc] No glEGLImageTargetTexture2DOES??"); return; } } if (!g_pHyprlock->dma.linuxDmabuf) { Log::logger->log(Log::ERR, "[sc] No DMABUF support?"); return; } if (!g_pHyprlock->dma.gbmDevice) { Log::logger->log(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) { Log::logger->log(Log::TRACE, "[sc] wlrOnDmabuf for {}", (void*)this); m_w = width; m_h = height; m_fmt = format; Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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 { Log::logger->log(Log::INFO, "eglQueryDmaBufModifiersEXT found {} mods", num); std::vector goodMods; for (int i = 0; i < num; ++i) { if (externalOnly[i]) { Log::logger->log(Log::TRACE, "Modifier {:x} failed test", mods[i]); continue; } Log::logger->log(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) { Log::logger->log(Log::ERR, "[bo] Couldn't create a drm buffer"); return false; } m_planes = gbm_bo_get_plane_count(m_bo); Log::logger->log(Log::INFO, "[bo] has {} plane(s)", m_planes); m_mod = gbm_bo_get_modifier(m_bo); Log::logger->log(Log::INFO, "[bo] chose modifier {:x}", m_mod); auto params = makeShared(g_pHyprlock->dma.linuxDmabuf->sendCreateParams()); if (!params) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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 texture) { 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) { Log::logger->log(Log::ERR, "Failed creating an egl image"); return false; } texture->allocate(); texture->m_vSize = {m_w, m_h}; glBindTexture(GL_TEXTURE_2D, 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); Log::logger->log(Log::INFO, "Got dma frame with size {}", texture->m_vSize); return true; } CSCSHMFrame::CSCSHMFrame(SP sc) : m_sc(sc) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(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) { Log::logger->log(Log::ERR, "[sc] [shm] failed to (errno {})", strerror(errno)); close(FD); m_ok = false; return; } if (!g_pHyprlock->getShm()) { Log::logger->log(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: { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::WARN, "[sc] [shm] Unsupported format {}", m_shmFmt); } } } else if (BYTESPERPX == 3) { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::INFO, "[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: { Log::logger->log(Log::ERR, "[sc] [shm] Unsupported format for 24bit buffer {}", m_shmFmt); } } } else { Log::logger->log(Log::ERR, "[sc] [shm] Unsupported bytes per pixel {}", BYTESPERPX); } } bool CSCSHMFrame::onBufferReady(ASP texture) { convertBuffer(); texture->allocate(); texture->m_vSize.x = m_w; texture->m_vSize.y = m_h; glBindTexture(GL_TEXTURE_2D, 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); Log::logger->log(Log::INFO, "[sc] [shm] Got screenshot with size {}", texture->m_vSize); return true; } hyprwm-hyprlock-d75e93f/src/renderer/Screencopy.hpp000066400000000000000000000045041517065114500225510ustar00rootroot00000000000000#pragma once #include "../defines.hpp" #include "../core/Output.hpp" #include "../renderer/Texture.hpp" #include #include #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: CScreencopyFrame() = default; ~CScreencopyFrame() = default; void capture(SP pOutput); SP m_sc = nullptr; size_t m_resourceID; ASP m_asset; bool m_ready = false; private: 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 texture); 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; }; hyprwm-hyprlock-d75e93f/src/renderer/Shader.cpp000066400000000000000000000007131517065114500216360ustar00rootroot00000000000000#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; }hyprwm-hyprlock-d75e93f/src/renderer/Shader.hpp000066400000000000000000000036051517065114500216460ustar00rootroot00000000000000#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; };hyprwm-hyprlock-d75e93f/src/renderer/Shaders.hpp000066400000000000000000000405621517065114500220340ustar00rootroot00000000000000#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; } )#";hyprwm-hyprlock-d75e93f/src/renderer/Shared.hpp000066400000000000000000000002141517065114500216370ustar00rootroot00000000000000#pragma once #include "Texture.hpp" #include "../defines.hpp" struct SPreloadedAsset { CTexture texture; bool ready = false; };hyprwm-hyprlock-d75e93f/src/renderer/Texture.cpp000066400000000000000000000006021517065114500220650ustar00rootroot00000000000000#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; } hyprwm-hyprlock-d75e93f/src/renderer/Texture.hpp000066400000000000000000000010411517065114500220700ustar00rootroot00000000000000#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; };hyprwm-hyprlock-d75e93f/src/renderer/mtx.hpp000066400000000000000000000133501517065114500212460ustar00rootroot00000000000000 #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; } hyprwm-hyprlock-d75e93f/src/renderer/resources/000077500000000000000000000000001517065114500217355ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/renderer/resources/TextCmdResource.cpp000066400000000000000000000015701517065114500255240ustar00rootroot00000000000000 #include "TextCmdResource.hpp" #include "../../config/ConfigManager.hpp" #include "../../helpers/MiscFunctions.hpp" #include using namespace Hyprgraphics; CTextCmdResource::CTextCmdResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) { ; } void CTextCmdResource::render() { static const auto TRIM = g_pConfigManager->getValue("general:text_trim"); CTextResource::STextResourceData textData = m_data; textData.text = spawnSync(m_data.text); if (*TRIM) { textData.text.erase(0, textData.text.find_first_not_of(" \n\r\t")); textData.text.erase(textData.text.find_last_not_of(" \n\r\t") + 1); } Hyprgraphics::CTextResource textResource(std::move(textData)); textResource.render(); std::swap(m_asset, textResource.m_asset); } hyprwm-hyprlock-d75e93f/src/renderer/resources/TextCmdResource.hpp000066400000000000000000000006571517065114500255360ustar00rootroot00000000000000#pragma once #include #include class CTextCmdResource : public Hyprgraphics::IAsyncResource { public: CTextCmdResource(Hyprgraphics::CTextResource::STextResourceData&& data); virtual ~CTextCmdResource() = default; virtual void render(); private: Hyprgraphics::CTextResource::STextResourceData m_data; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/000077500000000000000000000000001517065114500213715ustar00rootroot00000000000000hyprwm-hyprlock-d75e93f/src/renderer/widgets/Background.cpp000066400000000000000000000267361517065114500241720ustar00rootroot00000000000000#include "Background.hpp" #include "../Renderer.hpp" #include "../AsyncResourceManager.hpp" #include "../Framebuffer.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; } static std::string runAndGetPath(const std::string& reloadCommand) { std::string path = spawnSync(reloadCommand); if (path.ends_with('\0')) path.pop_back(); if (path.ends_with('\n')) path.pop_back(); if (path.starts_with("file://")) path = path.substr(7); return path; } 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 = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort); g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); if (!g_asyncResourceManager->checkIdPresent(scResourceID)) { Log::logger->log(Log::INFO, "Missing screenshot for output {}", outputPort); scResourceID = 0; } if (!reloadCommand.empty() && path.empty()) path = runAndGetPath(reloadCommand); if (isScreenshot) { resourceID = scResourceID; // Fallback to solid background:color when scResourceID==0 if (!g_pHyprlock->getScreencopy()) { Log::logger->log(Log::ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); resourceID = 0; } } else if (!path.empty()) resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); if (!reloadCommand.empty() && reloadTime > -1) { try { if (!isScreenshot) modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); } catch (std::exception& e) { Log::logger->log(Log::ERR, "{}", e.what()); } plantReloadTimer(); // No reloads if reloadCommand is empty } } void CBackground::reset() { if (reloadTimer) { reloadTimer->cancel(); reloadTimer.reset(); } blurredFB->destroyBuffer(); pendingBlurredFB->destroyBuffer(); } void CBackground::updatePrimaryAsset() { if (asset || resourceID == 0) return; asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return; const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender); if (NEEDFB) renderToFB(*asset, *blurredFB, blurPasses, isScreenshot); } void CBackground::updatePendingAsset() { // For crossfading a new asset if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated()) return; renderToFB(*pendingAsset, *pendingBlurredFB, blurPasses); } void CBackground::updateScAsset() { if (scAsset || scResourceID == 0) return; // path=screenshot -> scAsset = asset scAsset = (asset && isScreenshot) ? asset : g_asyncResourceManager->getAssetByID(scResourceID); if (!scAsset) return; const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL; if (NEEDSCTRANSFORM) renderToFB(*scAsset, *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; } const CTexture& CBackground::getPendingAssetTex() const { return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : *pendingAsset; } const CTexture& CBackground::getScAssetTex() const { return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : *scAsset; } 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 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->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(asset); resourceID = 0; renderRect(color); return false; } if (!asset || resourceID == 0) { // 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 > 0; // 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::onAssetUpdate(ResourceID id, ASP newAsset) { pendingResource = false; if (!newAsset) Log::logger->log(Log::ERR, "Background asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Log::logger->log(Log::ERR, "New background asset has an invalid texture!"); } else { pendingAsset = newAsset; crossFadeProgress->setValueAndWarp(0); *crossFadeProgress = 1.0; crossFadeProgress->setCallbackOnEnd( [REF = m_self, id](auto) { if (const auto PSELF = REF.lock()) { if (PSELF->asset) g_asyncResourceManager->unload(PSELF->asset); PSELF->asset = PSELF->pendingAsset; PSELF->pendingAsset = nullptr; PSELF->resourceID = id; PSELF->blurredFB->destroyBuffer(); PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); } }, true); } } 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 = runAndGetPath(reloadCommand); if (path.empty()) return; } try { const auto MTIME = std::filesystem::last_write_time(absolutePath(path, "")); if (OLDPATH == path && MTIME == modificationTime) return; modificationTime = MTIME; if (OLDPATH == path) m_imageRevision++; else m_imageRevision = 0; } catch (std::exception& e) { path = OLDPATH; Log::logger->log(Log::ERR, "{}", e.what()); return; } if (pendingResource) return; pendingResource = true; // Issue the next request AWP widget(m_self); g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/Background.hpp000066400000000000000000000057531517065114500241730ustar00rootroot00000000000000#pragma once #include "IWidget.hpp" #include "../../defines.hpp" #include "../../helpers/AnimatedVariable.hpp" #include "../../helpers/Color.hpp" #include "../../core/Timer.hpp" #include "../Framebuffer.hpp" #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); virtual void onAssetUpdate(ResourceID id, ASP newAsset); 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; ResourceID resourceID = 0; ResourceID scResourceID = 0; bool pendingResource = false; 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; ASP reloadTimer; std::filesystem::file_time_type modificationTime; size_t m_imageRevision = 0; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/IWidget.cpp000066400000000000000000000241621517065114500234360ustar00rootroot00000000000000#include "IWidget.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "../../auth/Auth.hpp" #include #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") Log::logger->log(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") Log::logger->log(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); } Hyprgraphics::CTextResource::eTextAlignmentMode IWidget::parseTextAlignment(const std::string& alignment) { Hyprgraphics::CTextResource::eTextAlignmentMode align = Hyprgraphics::CTextResource::TEXT_ALIGN_LEFT; if (alignment == "center") align = Hyprgraphics::CTextResource::TEXT_ALIGN_CENTER; else if (alignment == "right") align = Hyprgraphics::CTextResource::TEXT_ALIGN_RIGHT; return align; } 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()) { Log::logger->log(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&) { Log::logger->log(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) { Log::logger->log(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 ? uidPassword->pw_name : nullptr; char* user_gecos = uidPassword ? uidPassword->pw_gecos : nullptr; if (!username) Log::logger->log(Log::ERR, "Error in formatString, username null. Errno: ", errno); if (!user_gecos) Log::logger->log(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) { Log::logger->log(Log::ERR, "Error parsing {} in cmd[]", v); } } else { Log::logger->log(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); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/IWidget.hpp000066400000000000000000000037271517065114500234470ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "../../helpers/Math.hpp" #include "../../core/Seat.hpp" #include "../Texture.hpp" #include #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; // Never render within onAssetUpdate! virtual void onAssetUpdate(ResourceID id, ASP newAsset) = 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); static Hyprgraphics::CTextResource::eTextAlignmentMode parseTextAlignment(const std::string& alignment); 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; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/Image.cpp000066400000000000000000000170151517065114500231230ustar00rootroot00000000000000#include "Image.hpp" #include "../Renderer.hpp" #include "../AsyncResourceManager.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(); } } void CImage::onTimerUpdate() { if (m_pendingResource) { Log::logger->log(Log::WARN, "Trying to update image, but a resource is still pending! Skipping update."); return; } 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; if (OLDPATH == path) m_imageRevision++; else m_imageRevision = 0; } catch (std::exception& e) { path = OLDPATH; Log::logger->log(Log::ERR, "{}", e.what()); return; } m_pendingResource = true; AWP widget(m_self); g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } 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 = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); angle = angle * M_PI / 180.0; if (reloadTime > -1) { try { modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); } catch (std::exception& e) { Log::logger->log(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_asyncResourceManager->unload(asset); asset = nullptr; m_pendingResource = false; resourceID = 0; } bool CImage::draw(const SRenderData& data) { if (resourceID == 0) return false; if (!asset) asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return true; if (asset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(asset); resourceID = 0; return false; } if (!imageFB.isAllocated()) { const Vector2D IMAGEPOS = {border, border}; const Vector2D BORDERPOS = {0.0, 0.0}; const Vector2D TEXSIZE = asset->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, 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::onAssetUpdate(ResourceID id, ASP newAsset) { m_pendingResource = false; if (!newAsset) Log::logger->log(Log::ERR, "asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Log::logger->log(Log::ERR, "New image asset has an invalid texture!"); } else { g_asyncResourceManager->unload(asset); imageFB.destroyBuffer(); asset = newAsset; resourceID = id; firstRender = true; } } 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); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/Image.hpp000066400000000000000000000042331517065114500231260ustar00rootroot00000000000000#pragma once #include "IWidget.hpp" #include "../../defines.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../core/Timer.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 void onAssetUpdate(ResourceID id, ASP newAsset); 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 = 0; int rounding = 0; double border = 0; double angle = 0; 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; size_t m_imageRevision = 0; ASP imageTimer; Vector2D viewport; std::string stringPort; ResourceID resourceID = 0; bool m_pendingResource = false; ASP asset = nullptr; CShadowable shadow; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/Label.cpp000066400000000000000000000137011517065114500231160ustar00rootroot00000000000000#include "Label.hpp" #include "../Renderer.hpp" #include "../AsyncResourceManager.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" #include "src/defines.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(); } } void CLabel::onTimerUpdate() { if (m_pendingResource) { Log::logger->log(Log::WARN, "Trying to update label, but a resource is still pending! Skipping update."); return; } std::string oldFormatted = label.formatted; label = formatString(labelPreFormat); if (label.formatted == oldFormatted && !label.alwaysUpdate) return; // request new request.text = label.formatted; m_pendingResource = true; AWP widget(m_self); if (label.cmd) { // Don't increment by one to avoid clashes with multiple widget using the same label command. m_dynamicRevision += (label.updateEveryMs == 0) ? 1 : label.updateEveryMs; g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock()); } else g_asyncResourceManager->requestText(request, widget.lock()); } 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")); m_angle = std::any_cast(props.at("rotate")); m_angle = m_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.text = label.formatted; request.font = fontFamily; request.fontSize = fontSize; request.color = labelColor.asRGB(); m_alpha = labelColor.a; if (!textAlign.empty()) request.align = parseTextAlignment(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 if (label.cmd) { resourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, nullptr); } else resourceID = g_asyncResourceManager->requestText(request, nullptr); plantTimer(); } void CLabel::reset() { if (labelTimer) { labelTimer->cancel(); labelTimer.reset(); } if (g_pHyprlock->m_bTerminate) return; if (asset) g_asyncResourceManager->unload(asset); asset = nullptr; m_pendingResource = false; resourceID = 0; } bool CLabel::draw(const SRenderData& data) { if (!asset) { asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return true; } if (updateShadow) { updateShadow = false; shadow.markShadowDirty(); } shadow.draw(data); // calc pos pos = posFromHVAlign(viewport, asset->m_vSize, configPos, halign, valign, m_angle); CBox box = {pos.x, pos.y, asset->m_vSize.x, asset->m_vSize.y}; box.rot = m_angle; g_pRenderer->renderTexture(box, *asset, data.opacity * m_alpha); return false; } void CLabel::onAssetUpdate(ResourceID id, ASP newAsset) { Log::logger->log(Log::TRACE, "Label update for resourceID {}", id); m_pendingResource = false; if (!newAsset) Log::logger->log(Log::ERR, "asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Log::logger->log(Log::ERR, "New image asset has an invalid texture!"); } else { // new asset is ready :D g_asyncResourceManager->unload(asset); asset = newAsset; resourceID = id; updateShadow = true; } } CBox CLabel::getBoundingBoxWl() const { if (!asset) return CBox{}; return { Vector2D{pos.x, viewport.y - pos.y - asset->m_vSize.y}, asset->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); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/Label.hpp000066400000000000000000000043661517065114500231320ustar00rootroot00000000000000#pragma once #include "../../defines.hpp" #include "IWidget.hpp" #include "Shadowable.hpp" #include "../../core/Timer.hpp" #include #include #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 void onAssetUpdate(ResourceID id, ASP newAsset); 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 labelPreFormat; IWidget::SFormatResult label; std::string halign, valign; std::string onclickCommand; Vector2D viewport; Vector2D pos; Vector2D configPos; double m_angle = 0; double m_alpha = 0; ResourceID resourceID = 0; bool m_pendingResource = false; size_t m_dynamicRevision = 0; ASP asset = nullptr; std::string outputStringPort; Hyprgraphics::CTextResource::STextResourceData request; ASP labelTimer = nullptr; CShadowable shadow; bool updateShadow = true; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/PasswordInputField.cpp000066400000000000000000000460201517065114500256650ustar00rootroot00000000000000#include "PasswordInputField.hpp" #include "../AsyncResourceManager.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 #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")); configCheckText = std::any_cast(props.at("check_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.001f, 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()) { Hyprgraphics::CTextResource::STextResourceData request; request.text = dots.textFormat; request.font = fontFamily; request.color = colorConfig.font.asRGB(); request.fontSize = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f); dots.textResourceID = g_asyncResourceManager->requestText(request, nullptr); } // request the inital placeholder asset updatePlaceholder(); } void CPasswordInputField::reset() { if (fade.fadeOutTimer) { fade.fadeOutTimer->cancel(); fade.fadeOutTimer.reset(); } if (g_pHyprlock->m_bTerminate) return; if (placeholder.asset) g_asyncResourceManager->unload(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID = 0; 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) 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 && configCheckText.empty()) 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_asyncResourceManager->getAssetByID(dots.textResourceID); if (!dots.textAsset) forceReload = true; else { passSize = dots.textAsset->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, fontCol.a, dots.rounding); } else g_pRenderer->renderRect(box, fontCol, dots.rounding); fontCol.a = DOTALPHA; } } bool placeholderPasswordCondition = (passwordLength == 0 && placeholder.resourceID > 0); if (placeholderPasswordCondition && (!checkWaiting || (checkWaiting && !configCheckText.empty()))) { ASP currAsset = nullptr; if (!placeholder.asset) placeholder.asset = g_asyncResourceManager->getAssetByID(placeholder.resourceID); currAsset = placeholder.asset; if (currAsset) { const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->m_vSize / 2.0; const CBox ASSETBOX{ASSETPOS, currAsset->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, 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) { g_asyncResourceManager->unload(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID = 0; redrawShadow = true; } return; } // already requested a placeholder for the current fail if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts()) return; std::string newText = ""; if (displayFail) { newText = formatString(configFailText).formatted; placeholder.failedAttempts = g_pAuth->getFailedAttempts(); } else if (checkWaiting && !configCheckText.empty()) newText = formatString(configCheckText).formatted; else newText = 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; Log::logger->log(Log::INFO, "Updating placeholder text: {}", newText); placeholder.currentText = newText; placeholder.asset = nullptr; // query Hyprgraphics::CTextResource::STextResourceData request; request.text = placeholder.currentText; request.font = fontFamily; request.color = colorState.font.asRGB(); request.fontSize = (int)size->value().y / 4; AWP widget(m_self); placeholder.resourceID = g_asyncResourceManager->requestText(request, widget); } void CPasswordInputField::onAssetUpdate(ResourceID id, ASP newAsset) { ; } void CPasswordInputField::updateWidth() { double targetSizeX = configSize.x; if (passwordLength == 0 && placeholder.asset) targetSizeX = placeholder.asset->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 = colorConfig.font; if (displayFail) fontTarget = colorConfig.fail->m_vColors.front(); else if (checkWaiting) fontTarget = configCheckText.empty() ? colorConfig.font : colorConfig.check->m_vColors.front(); 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); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/PasswordInputField.hpp000066400000000000000000000077471517065114500257070ustar00rootroot00000000000000#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 onAssetUpdate(ResourceID id, ASP newAsset); 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, configCheckText, 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; size_t textResourceID = 0; std::string textFormat = ""; ASP textAsset = nullptr; } dots; struct { PHLANIMVAR a; bool appearing = true; ASP fadeOutTimer = nullptr; bool allowFadeOut = false; } fade; struct { size_t resourceID = 0; ASP asset = nullptr; std::string currentText = ""; size_t failedAttempts = 0; } 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; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/Shadowable.cpp000066400000000000000000000027501517065114500241520ustar00rootroot00000000000000#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; } hyprwm-hyprlock-d75e93f/src/renderer/widgets/Shadowable.hpp000066400000000000000000000016501517065114500241550ustar00rootroot00000000000000#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; }; hyprwm-hyprlock-d75e93f/src/renderer/widgets/Shape.cpp000066400000000000000000000105771517065114500231470ustar00rootroot00000000000000#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); Log::logger->log(Log::INFO, "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; } void CShape::onAssetUpdate(ResourceID id, ASP newAsset) { ; } 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); } hyprwm-hyprlock-d75e93f/src/renderer/widgets/Shape.hpp000066400000000000000000000025421517065114500231450ustar00rootroot00000000000000#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 void onAssetUpdate(ResourceID id, ASP newAsset); 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; };