Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
X11:Wayland
hyprlock
hyprlock-0.4.1.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File hyprlock-0.4.1.obscpio of Package hyprlock
07070100000000000081A4000000000000000000000001669CF85C0000070E000000000000000000000000000000000000001D00000000hyprlock-0.4.1/.clang-format--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All 07070100000001000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001700000000hyprlock-0.4.1/.github07070100000002000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000002100000000hyprlock-0.4.1/.github/workflows07070100000003000081A4000000000000000000000001669CF85C000001EE000000000000000000000000000000000000002900000000hyprlock-0.4.1/.github/workflows/nix.ymlname: Build on: [push, pull_request, workflow_dispatch] jobs: nix: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build run: nix flake check --print-build-logs --keep-going 07070100000004000081A4000000000000000000000001669CF85C0000009B000000000000000000000000000000000000001A00000000hyprlock-0.4.1/.gitignore**/.cache .direnv .envrc .vscode/ CMakeCache.txt CMakeFiles/ Makefile cmake_install.cmake build/ compile_commands.json protocols/*.c protocols/*.h *.kdev4 07070100000005000081A4000000000000000000000001669CF85C00000EFD000000000000000000000000000000000000001E00000000hyprlock-0.4.1/CMakeLists.txtcmake_minimum_required(VERSION 3.19) 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) # 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) pkg_check_modules( deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon libjpeg libwebp libmagic cairo pangocairo libdrm gbm hyprutils>=0.2.0) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(hyprlock ${SRCFILES}) target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps OpenGL::EGL OpenGL::GL) # protocols find_program(WaylandScanner NAMES wayland-scanner) message(STATUS "Found WaylandScanner at ${WaylandScanner}") execute_process( COMMAND pkg-config --variable=pkgdatadir wayland-protocols WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") function(protocol protoPath protoName external) if(external) execute_process( COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) execute_process( COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c) else() execute_process( COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) execute_process( COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c) endif() endfunction() make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so # the dir won't be there protocol("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false) protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false) protocol("unstable/tablet/tablet-unstable-v2.xml" "tablet-unstable-v2" false) protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false) protocol("stable/viewporter/viewporter.xml" "viewporter" false) protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true) protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false) # Installation install(TARGETS hyprlock) install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d) 07070100000006000081A4000000000000000000000001669CF85C000005DF000000000000000000000000000000000000001700000000hyprlock-0.4.1/LICENSEBSD 3-Clause License Copyright (c) 2024, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 07070100000007000081A4000000000000000000000001669CF85C00000514000000000000000000000000000000000000001900000000hyprlock-0.4.1/README.md# hyprlock Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility. ## Features - uses the secure ext-session-lock protocol - full support for fractional-scale - fully GPU accelerated - multi-threaded resource acquisition for no hitches ## 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 also need the following dependencies - wayland-client - wayland-protocols - mesa And the development libraries for the following - cairo - libdrm - pango - xkbcommon - pam - hyprlang >= 0.4 - libmagic (file-devel on Fedora) Development libraries are usually suffixed with `-devel` or `-dev` in most distro repos. You also need to install `mesa-libgbm-devel` on some distros like RPM based ones where its not bundled with the mesa package. ### 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 ``` 07070100000008000081A4000000000000000000000001669CF85C00000006000000000000000000000000000000000000001700000000hyprlock-0.4.1/VERSION0.4.1 07070100000009000081A4000000000000000000000001669CF85C000008B4000000000000000000000000000000000000001A00000000hyprlock-0.4.1/flake.lock{ "nodes": { "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1721324361, "narHash": "sha256-BiJKO0IIdnSwHQBSrEJlKlFr753urkLE48wtt0UhNG4=", "owner": "hyprwm", "repo": "hyprlang", "rev": "adbefbf49664a6c2c8bf36b6487fd31e3eb68086", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1721324102, "narHash": "sha256-WAZ0X6yJW1hFG6otkHBfyJDKRpNP5stsRqdEuHrFRpk=", "owner": "hyprwm", "repo": "hyprutils", "rev": "962582a090bc233c4de9d9897f46794280288989", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1721138476, "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=", "owner": "NixOS", "repo": "nixpkgs", "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprlang": "hyprlang", "hyprutils": "hyprutils", "nixpkgs": "nixpkgs", "systems": "systems" } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } } }, "root": "root", "version": 7 } 0707010000000A000081A4000000000000000000000001669CF85C0000057D000000000000000000000000000000000000001900000000hyprlock-0.4.1/flake.nix{ description = "Hyprland's GPU-accelerated screen locking utility"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprlang = { url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; }; outputs = { self, nixpkgs, systems, ... } @ inputs: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [default]; }); in { overlays = import ./nix/overlays.nix {inherit inputs lib;}; packages = eachSystem (system: { default = self.packages.${system}.hyprlock; inherit (pkgsFor.${system}) hyprlock; }); homeManagerModules = { default = self.homeManagerModules.hyprlock; hyprlock = builtins.throw "hyprlock: the flake HM module has been removed. Use the module from Home Manager upstream."; }; checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } 0707010000000B000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001300000000hyprlock-0.4.1/nix0707010000000C000081A4000000000000000000000001669CF85C0000030D000000000000000000000000000000000000001F00000000hyprlock-0.4.1/nix/default.nix{ lib, stdenv, cmake, pkg-config, cairo, file, libdrm, libGL, libjpeg, libwebp, libxkbcommon, mesa, hyprlang, hyprutils, pam, pango, wayland, wayland-protocols, version ? "git", }: stdenv.mkDerivation { pname = "hyprlock"; inherit version; src = ../.; nativeBuildInputs = [ cmake pkg-config ]; buildInputs = [ cairo file libdrm libGL libjpeg libwebp libxkbcommon mesa hyprlang hyprutils pam pango wayland wayland-protocols ]; 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"; }; } 0707010000000D000081A4000000000000000000000001669CF85C000002D5000000000000000000000000000000000000002000000000hyprlock-0.4.1/nix/overlays.nix{ lib, inputs, }: let mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); in { default = inputs.self.overlays.hyprlock; hyprlock = lib.composeManyExtensions [ inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default (final: prev: { hyprlock = prev.callPackage ./default.nix { stdenv = prev.gcc13Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); inherit (final) hyprlang; }; }) ]; } 0707010000000E000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001300000000hyprlock-0.4.1/pam0707010000000F000081A4000000000000000000000001669CF85C0000007E000000000000000000000000000000000000001C00000000hyprlock-0.4.1/pam/hyprlock# PAM configuration file for hyprlock # the 'login' configuration file (see /etc/pam.d/login) auth include login 07070100000010000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001900000000hyprlock-0.4.1/protocols07070100000011000081A4000000000000000000000001669CF85C00002585000000000000000000000000000000000000003800000000hyprlock-0.4.1/protocols/wlr-screencopy-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_screencopy_unstable_v1"> <copyright> 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. </copyright> <description summary="screen content capturing on client buffers"> 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. </description> <interface name="zwlr_screencopy_manager_v1" version="3"> <description summary="manager to inform clients and begin capturing"> This object is a manager which offers requests to start capturing from a source. </description> <request name="capture_output"> <description summary="capture an output"> Capture the next frame of an entire output. </description> <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/> <arg name="overlay_cursor" type="int" summary="composite cursor onto the frame"/> <arg name="output" type="object" interface="wl_output"/> </request> <request name="capture_output_region"> <description summary="capture an output's region"> 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. </description> <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/> <arg name="overlay_cursor" type="int" summary="composite cursor onto the frame"/> <arg name="output" type="object" interface="wl_output"/> <arg name="x" type="int"/> <arg name="y" type="int"/> <arg name="width" type="int"/> <arg name="height" type="int"/> </request> <request name="destroy" type="destructor"> <description summary="destroy the manager"> All objects created by the manager will still remain valid, until their appropriate destroy request has been called. </description> </request> </interface> <interface name="zwlr_screencopy_frame_v1" version="3"> <description summary="a frame ready for copy"> 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. </description> <event name="buffer"> <description summary="wl_shm buffer information"> 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. </description> <arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/> <arg name="width" type="uint" summary="buffer width"/> <arg name="height" type="uint" summary="buffer height"/> <arg name="stride" type="uint" summary="buffer stride"/> </event> <request name="copy"> <description summary="copy the frame"> 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. </description> <arg name="buffer" type="object" interface="wl_buffer"/> </request> <enum name="error"> <entry name="already_used" value="0" summary="the object has already been used to copy a wl_buffer"/> <entry name="invalid_buffer" value="1" summary="buffer attributes are invalid"/> </enum> <enum name="flags" bitfield="true"> <entry name="y_invert" value="1" summary="contents are y-inverted"/> </enum> <event name="flags"> <description summary="frame flags"> Provides flags about the frame. This event is sent once before the "ready" event. </description> <arg name="flags" type="uint" enum="flags" summary="frame flags"/> </event> <event name="ready"> <description summary="indicates frame is available for reading"> 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. </description> <arg name="tv_sec_hi" type="uint" summary="high 32 bits of the seconds part of the timestamp"/> <arg name="tv_sec_lo" type="uint" summary="low 32 bits of the seconds part of the timestamp"/> <arg name="tv_nsec" type="uint" summary="nanoseconds part of the timestamp"/> </event> <event name="failed"> <description summary="frame copy failed"> This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. </description> </event> <request name="destroy" type="destructor"> <description summary="delete this object, used or not"> Destroys the frame. This request can be sent at any time by the client. </description> </request> <!-- Version 2 additions --> <request name="copy_with_damage" since="2"> <description summary="copy the frame when it's damaged"> Same as copy, except it waits until there is damage to copy. </description> <arg name="buffer" type="object" interface="wl_buffer"/> </request> <event name="damage" since="2"> <description summary="carries the coordinates of the damaged region"> 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. </description> <arg name="x" type="uint" summary="damaged x coordinates"/> <arg name="y" type="uint" summary="damaged y coordinates"/> <arg name="width" type="uint" summary="current width"/> <arg name="height" type="uint" summary="current height"/> </event> <!-- Version 3 additions --> <event name="linux_dmabuf" since="3"> <description summary="linux-dmabuf buffer information"> 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. </description> <arg name="format" type="uint" summary="fourcc pixel format"/> <arg name="width" type="uint" summary="buffer width"/> <arg name="height" type="uint" summary="buffer height"/> </event> <event name="buffer_done" since="3"> <description summary="all buffer types reported"> 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. </description> </event> </interface> </protocol> 07070100000012000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001300000000hyprlock-0.4.1/src07070100000013000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001A00000000hyprlock-0.4.1/src/config07070100000014000081A4000000000000000000000001669CF85C000055C9000000000000000000000000000000000000002C00000000hyprlock-0.4.1/src/config/ConfigManager.cpp#include "ConfigManager.hpp" #include "../helpers/MiscFunctions.hpp" #include "src/helpers/Log.hpp" #include <hyprutils/path/Path.hpp> #include <filesystem> #include <glob.h> #include <cstring> #include <mutex> 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 std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprlock"); if (paths.first.has_value()) return paths.first.value(); else throw std::runtime_error("Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr."); } CConfigManager::CConfigManager(std::string configPath) : m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = configPath.empty()}) { configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath; } 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}); m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0}); m_config.addConfigValue("general:text_trim", Hyprlang::INT{1}); m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0}); m_config.addConfigValue("general:grace", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_out", 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:pam_module", Hyprlang::STRING{"hyprlock"}); 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.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("shape", "size", Hyprlang::VEC2{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", Hyprlang::INT{0xFF00CFE6}); m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111}); m_config.addSpecialConfigValue("shape", "position", Hyprlang::VEC2{0, 80}); 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"); 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", Hyprlang::INT{0xFFDDDDDD}); m_config.addSpecialConfigValue("image", "position", Hyprlang::VEC2{0, 200}); 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"); 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", Hyprlang::VEC2{400, 90}); m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD}); m_config.addSpecialConfigValue("input-field", "outer_color", Hyprlang::INT{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", "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", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("input-field", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("input-field", "position", Hyprlang::VEC2{0, -20}); m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"}); m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("input-field", "check_color", Hyprlang::INT{0xFFCC8822}); m_config.addSpecialConfigValue("input-field", "fail_color", Hyprlang::INT{0xFFCC2222}); m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"}); m_config.addSpecialConfigValue("input-field", "fail_timeout", Hyprlang::INT{2000}); m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300}); m_config.addSpecialConfigValue("input-field", "capslock_color", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("input-field", "numlock_color", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("input-field", "bothlock_color", Hyprlang::INT{-1}); 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", Hyprlang::VEC2{400, 90}); 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{"none"}); m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"none"}); 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"); m_config.registerHandler(&::handleSource, "source", {false}); m_config.commence(); auto result = m_config.parse(); if (result.error) Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError()); #undef SHADOWABLE } std::mutex configMtx; void* const* CConfigManager::getValuePtr(const std::string& name) { std::lock_guard<std::mutex> lg(configMtx); return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr(); } std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() { std::vector<CConfigManager::SWidgetConfig> 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()) \ } // auto keys = m_config.listKeysForSpecialCategory("background"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ "background", std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())), { {"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())}, } }); // clang-format on } // keys = m_config.listKeysForSpecialCategory("shape"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ "shape", std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())), { {"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"), } }); // clang-format on } // keys = m_config.listKeysForSpecialCategory("image"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ "image", std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("image", "monitor", k.c_str())), { {"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"), } }); // clang-format on } keys = m_config.listKeysForSpecialCategory("input-field"); for (auto& k : keys) { // clang-format off result.push_back(CConfigManager::SWidgetConfig{ "input-field", std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())), { {"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())}, {"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())}, {"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())}, {"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())}, {"fail_timeout", m_config.getSpecialConfigValue("input-field", "fail_timeout", k.c_str())}, {"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", 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{ "label", std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())), { {"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"), } }); // clang-format on } return result; } std::optional<std::string> CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { if (rawpath.length() < 2) { Debug::log(ERR, "source= path garbage"); return "source path " + rawpath + " bogus!"; } std::unique_ptr<glob_t, void (*)(glob_t*)> glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; memset(glob_buf.get(), 0, sizeof(glob_t)); const auto CURRENTDIR = std::filesystem::path(configCurrentPath).parent_path().string(); if (auto r = glob(absolutePath(rawpath, CURRENTDIR).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); Debug::log(ERR, "{}", err); return err; } for (size_t i = 0; i < glob_buf->gl_pathc; i++) { const auto PATH = absolutePath(glob_buf->gl_pathv[i], CURRENTDIR); if (PATH.empty() || PATH == configCurrentPath) { Debug::log(WARN, "source= skipping invalid path"); continue; } if (!std::filesystem::is_regular_file(PATH)) { if (std::filesystem::exists(PATH)) { Debug::log(WARN, "source= skipping non-file {}", PATH); continue; } Debug::log(ERR, "source= file doesnt exist"); return "source file " + PATH + " doesn't exist!"; } // allow for nested config parsing auto backupConfigPath = configCurrentPath; configCurrentPath = PATH; m_config.parseFile(PATH.c_str()); configCurrentPath = backupConfigPath; } return {}; } 07070100000015000081A4000000000000000000000001669CF85C0000030B000000000000000000000000000000000000002C00000000hyprlock-0.4.1/src/config/ConfigManager.hpp#pragma once #include <hyprlang.hpp> #include <optional> #include <vector> #include <memory> #include <unordered_map> class CConfigManager { public: CConfigManager(std::string configPath); void init(); void* const* getValuePtr(const std::string& name); struct SWidgetConfig { std::string type; std::string monitor; std::unordered_map<std::string, std::any> values; }; std::vector<SWidgetConfig> getWidgetConfigs(); std::optional<std::string> handleSource(const std::string&, const std::string&); std::string configCurrentPath; private: Hyprlang::CConfig m_config; }; inline std::unique_ptr<CConfigManager> g_pConfigManager; 07070100000016000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001800000000hyprlock-0.4.1/src/core07070100000017000081A4000000000000000000000001669CF85C0000180E000000000000000000000000000000000000002100000000hyprlock-0.4.1/src/core/Auth.cpp#include "Auth.hpp" #include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "src/config/ConfigManager.hpp" #include <filesystem> #include <unistd.h> #include <pwd.h> #include <security/pam_appl.h> #if __has_include(<security/pam_misc.h>) #include <security/pam_misc.h> #endif #include <cstring> #include <thread> int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { const auto CONVERSATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr; struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); bool initialPrompt = true; for (int i = 0; i < num_msg; ++i) { switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: { const auto PROMPT = std::string(msg[i]->msg); const auto PROMPTCHANGED = PROMPT != CONVERSATIONSTATE->prompt; Debug::log(LOG, "PAM_PROMPT: {}", PROMPT); if (PROMPTCHANGED) g_pHyprlock->enqueueForceUpdateTimers(); // Some pam configurations ask for the password twice for whatever reason (Fedora su for example) // When the prompt is the same as the last one, I guess our answer can be the same. if (!initialPrompt && PROMPTCHANGED) { CONVERSATIONSTATE->prompt = PROMPT; g_pAuth->waitForInput(); } // Needed for unlocks via SIGUSR1 if (g_pHyprlock->isUnlocked()) return PAM_CONV_ERR; pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str()); initialPrompt = false; } break; case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break; case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break; } } *resp = pamReply; return PAM_SUCCESS; } CAuth::CAuth() { static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); m_sPamModule = *PPAMMODULE; if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) { Debug::log(ERR, "Pam module \"/etc/pam.d/{}\" does not exist! Falling back to \"/etc/pam.d/su\"", m_sPamModule); m_sPamModule = "su"; } } static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->onPasswordCheckTimer(); } void CAuth::start() { std::thread([this]() { resetConversation(); // Initial input m_sConversationState.prompt = "Password: "; waitForInput(); // For grace or SIGUSR1 unlocks if (g_pHyprlock->isUnlocked()) return; const auto AUTHENTICATED = auth(); m_bAuthenticated = AUTHENTICATED; // For SIGUSR1 unlocks if (g_pHyprlock->isUnlocked()) return; g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); }).detach(); } bool CAuth::auth() { const pam_conv localConv = {conv, (void*)&m_sConversationState}; pam_handle_t* handle = NULL; auto uidPassword = getpwuid(getuid()); int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle); if (ret != PAM_SUCCESS) { m_sConversationState.failText = "pam_start failed"; Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule); return false; } ret = pam_authenticate(handle, 0); pam_end(handle, ret); handle = nullptr; m_sConversationState.waitingForPamAuth = false; if (ret != PAM_SUCCESS) { m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule); return false; } m_sConversationState.failText = "Successfully authenticated"; Debug::log(LOG, "auth: authenticated for {}", m_sPamModule); return true; } bool CAuth::isAuthenticated() { return m_bAuthenticated; } // clearing the input must be done from the main thread static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->clearPasswordBuffer(); } void CAuth::waitForInput() { g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr); std::unique_lock<std::mutex> 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 CAuth::submitInput(std::string input) { std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex); if (!m_sConversationState.inputRequested) Debug::log(ERR, "SubmitInput called, but the auth thread is not waiting for input!"); m_sConversationState.input = input; m_sConversationState.inputRequested = false; m_sConversationState.waitingForPamAuth = true; m_sConversationState.inputSubmittedCondition.notify_all(); } std::optional<std::string> CAuth::getLastFailText() { return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText); } std::optional<std::string> CAuth::getLastPrompt() { return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt); } bool CAuth::checkWaiting() { return m_bBlockInput || m_sConversationState.waitingForPamAuth; } void CAuth::terminate() { m_sConversationState.inputSubmittedCondition.notify_all(); } void CAuth::resetConversation() { m_sConversationState.input = ""; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = false; } 07070100000018000081A4000000000000000000000001669CF85C0000055D000000000000000000000000000000000000002100000000hyprlock-0.4.1/src/core/Auth.hpp#pragma once #include <memory> #include <optional> #include <string> #include <mutex> #include <condition_variable> class CAuth { 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; }; CAuth(); void start(); bool auth(); bool isAuthenticated(); void waitForInput(); void submitInput(std::string input); std::optional<std::string> getLastFailText(); std::optional<std::string> getLastPrompt(); bool checkWaiting(); void terminate(); // Should only be set via the main thread bool m_bDisplayFailText = false; private: SPamConversationState m_sConversationState; bool m_bBlockInput = true; bool m_bAuthenticated = false; std::string m_sPamModule; void resetConversation(); }; inline std::unique_ptr<CAuth> g_pAuth; 07070100000019000081A4000000000000000000000001669CF85C00000247000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/core/CursorShape.cpp#include "CursorShape.hpp" #include "hyprlock.hpp" CCursorShape::CCursorShape(wp_cursor_shape_manager_v1* mgr) : mgr(mgr) { if (!g_pHyprlock->m_pPointer) return; dev = wp_cursor_shape_manager_v1_get_pointer(mgr, g_pHyprlock->m_pPointer); } void CCursorShape::setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape) { if (!dev) return; wp_cursor_shape_device_v1_set_shape(dev, serial, shape); } void CCursorShape::hideCursor(const uint32_t serial) { wl_pointer_set_cursor(g_pHyprlock->m_pPointer, serial, nullptr, 0, 0); }0707010000001A000081A4000000000000000000000001669CF85C00000193000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/core/CursorShape.hpp#pragma once #include <wayland-client.h> #include "cursor-shape-v1-protocol.h" class CCursorShape { public: CCursorShape(wp_cursor_shape_manager_v1* mgr); void setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape); void hideCursor(const uint32_t serial); private: wp_cursor_shape_manager_v1* mgr = nullptr; wp_cursor_shape_device_v1* dev = nullptr; };0707010000001B000081A4000000000000000000000001669CF85C00000B01000000000000000000000000000000000000002000000000hyprlock-0.4.1/src/core/Egl.cpp#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 == NULL) throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT"); eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); if (eglCreatePlatformWindowSurfaceEXT == NULL) throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT"); eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, NULL); EGLint matched = 0; if (eglDisplay == EGL_NO_DISPLAY) { Debug::log(CRIT, "Failed to create EGL display"); goto error; } if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE) { Debug::log(CRIT, "Failed to initialize EGL"); goto error; } if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) { Debug::log(CRIT, "eglChooseConfig failed"); goto error; } if (matched == 0) { Debug::log(CRIT, "Failed to match an EGL config"); goto error; } eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs); if (eglContext == EGL_NO_CONTEXT) { Debug::log(CRIT, "Failed to create EGL context"); goto error; } return; error: eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } CEGL::~CEGL() { if (eglContext != EGL_NO_CONTEXT) eglDestroyContext(eglDisplay, eglContext); if (eglDisplay) eglTerminate(eglDisplay); eglReleaseThread(); } void CEGL::makeCurrent(EGLSurface surf) { eglMakeCurrent(eglDisplay, surf, surf, eglContext); }0707010000001C000081A4000000000000000000000001669CF85C00000216000000000000000000000000000000000000002000000000hyprlock-0.4.1/src/core/Egl.hpp#pragma once #include <wayland-client.h> #include <memory> #include <EGL/egl.h> #include <EGL/eglext.h> class CEGL { public: CEGL(wl_display*); ~CEGL(); EGLDisplay eglDisplay; EGLConfig eglConfig; EGLContext eglContext; PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT; void makeCurrent(EGLSurface surf); }; inline std::unique_ptr<CEGL> g_pEGL;0707010000001D000081A4000000000000000000000001669CF85C00001448000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/core/LockSurface.cpp#include "LockSurface.hpp" #include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "Egl.hpp" #include "../renderer/Renderer.hpp" static void handleConfigure(void* data, ext_session_lock_surface_v1* surf, uint32_t serial, uint32_t width, uint32_t height) { const auto PSURF = (CSessionLockSurface*)data; PSURF->configure({width, height}, serial); } static const ext_session_lock_surface_v1_listener lockListener = { .configure = handleConfigure, }; static void handlePreferredScale(void* data, wp_fractional_scale_v1* wp_fractional_scale_v1, uint32_t scale) { const auto PSURF = (CSessionLockSurface*)data; PSURF->fractionalScale = scale / 120.0; Debug::log(LOG, "Got fractional scale: {}", PSURF->fractionalScale); if (PSURF->readyForFrame) PSURF->onScaleUpdate(); } static const wp_fractional_scale_v1_listener fsListener = { .preferred_scale = handlePreferredScale, }; CSessionLockSurface::~CSessionLockSurface() { if (fractional) { wp_viewport_destroy(viewport); wp_fractional_scale_v1_destroy(fractional); } if (eglWindow) wl_egl_window_destroy(eglWindow); if (lockSurface) ext_session_lock_surface_v1_destroy(lockSurface); if (surface) wl_surface_destroy(surface); if (frameCallback) wl_callback_destroy(frameCallback); } CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) { surface = wl_compositor_create_surface(g_pHyprlock->getCompositor()); if (!surface) { Debug::log(CRIT, "Couldn't create wl_surface"); exit(1); } const auto PFRACTIONALMGR = g_pHyprlock->getFractionalMgr(); const auto PVIEWPORTER = g_pHyprlock->getViewporter(); if (PFRACTIONALMGR && PVIEWPORTER) { fractional = wp_fractional_scale_manager_v1_get_fractional_scale(PFRACTIONALMGR, surface); if (fractional) { wp_fractional_scale_v1_add_listener(fractional, &fsListener, this); viewport = wp_viewporter_get_viewport(PVIEWPORTER, surface); } } if (!PFRACTIONALMGR || !fractional) Debug::log(LOG, "No fractional-scale support! Oops, won't be able to scale!"); if (!PVIEWPORTER) Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!"); lockSurface = ext_session_lock_v1_get_lock_surface(g_pHyprlock->getSessionLock(), surface, output->output); if (!lockSurface) { Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1"); exit(1); } ext_session_lock_surface_v1_add_listener(lockSurface, &lockListener, this); } void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) { Debug::log(LOG, "configure with serial {}", serial_); const bool sameSerial = serial == serial_; serial = serial_; logicalSize = size_; if (fractional) { size = (size_ * fractionalScale).floor(); wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y); } else { size = size_; } Debug::log(LOG, "Configuring surface for logical {} and pixel {}", logicalSize, size); if (!sameSerial) ext_session_lock_surface_v1_ack_configure(lockSurface, serial); wl_surface_set_buffer_scale(surface, 1); wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF); if (!eglWindow) { eglWindow = wl_egl_window_create(surface, size.x, size.y); if (!eglWindow) { Debug::log(CRIT, "Couldn't create eglWindow"); exit(1); } } else wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0); if (!eglSurface) { eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr); if (!eglSurface) { Debug::log(CRIT, "Couldn't create eglSurface: {}", (int)eglGetError()); // Clean up resources to prevent leaks wl_egl_window_destroy(eglWindow); eglWindow = nullptr; exit(1); // Consider graceful exit or fallback } } readyForFrame = true; render(); } void CSessionLockSurface::onScaleUpdate() { configure(logicalSize, serial); } static void handleDone(void* data, wl_callback* wl_callback, uint32_t callback_data) { const auto PSURF = (CSessionLockSurface*)data; if (g_pHyprlock->m_bTerminate) return; PSURF->onCallback(); } static const wl_callback_listener callbackListener = { .done = handleDone, }; void CSessionLockSurface::render() { Debug::log(TRACE, "render lock"); if (frameCallback || !readyForFrame) { needsFrame = true; return; } const auto FEEDBACK = g_pRenderer->renderLock(*this); frameCallback = wl_surface_frame(surface); wl_callback_add_listener(frameCallback, &callbackListener, this); eglSwapBuffers(g_pEGL->eglDisplay, eglSurface); needsFrame = FEEDBACK.needsFrame; } void CSessionLockSurface::onCallback() { wl_callback_destroy(frameCallback); frameCallback = nullptr; if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL) { needsFrame = false; render(); } }0707010000001E000081A4000000000000000000000001669CF85C000004F8000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/core/LockSurface.hpp#pragma once #include <wayland-client.h> #include "ext-session-lock-v1-protocol.h" #include "viewporter-protocol.h" #include "fractional-scale-v1-protocol.h" #include <wayland-egl.h> #include "../helpers/Vector2D.hpp" #include <EGL/egl.h> class COutput; class CRenderer; class CSessionLockSurface { public: CSessionLockSurface(COutput* output); ~CSessionLockSurface(); void configure(const Vector2D& size, uint32_t serial); bool readyForFrame = false; float fractionalScale = 1.0; void render(); void onCallback(); void onScaleUpdate(); private: COutput* output = nullptr; wl_surface* surface = nullptr; ext_session_lock_surface_v1* lockSurface = nullptr; uint32_t serial = 0; wl_egl_window* eglWindow = nullptr; Vector2D size; Vector2D logicalSize; EGLSurface eglSurface = nullptr; wp_fractional_scale_v1* fractional = nullptr; wp_viewport* viewport = nullptr; bool needsFrame = false; // wayland callbacks wl_callback* frameCallback = nullptr; friend class CRenderer; };0707010000001F000081A4000000000000000000000001669CF85C000009BC000000000000000000000000000000000000002300000000hyprlock-0.4.1/src/core/Output.cpp#include "Output.hpp" #include "../helpers/Log.hpp" #include "hyprlock.hpp" #include "../renderer/Renderer.hpp" static void handleGeometry(void* data, wl_output* output, 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) { const auto POUTPUT = (COutput*)data; POUTPUT->transform = (wl_output_transform)transform; Debug::log(LOG, "output {} make {} model {}", POUTPUT->name, make ? make : "", model ? model : ""); } static void handleMode(void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { const auto POUTPUT = (COutput*)data; // handle portrait mode and flipped cases if (POUTPUT->transform % 2 == 1) POUTPUT->size = {height, width}; else POUTPUT->size = {width, height}; } static void handleDone(void* data, wl_output* output) { const auto POUTPUT = (COutput*)data; Debug::log(LOG, "output {} done", POUTPUT->name); if (g_pHyprlock->m_bLocked && !POUTPUT->sessionLockSurface) { // if we are already locked, create a surface dynamically Debug::log(LOG, "Creating a surface dynamically for output as we are already locked"); POUTPUT->sessionLockSurface = std::make_unique<CSessionLockSurface>(POUTPUT); } } static void handleScale(void* data, wl_output* output, int32_t factor) { const auto POUTPUT = (COutput*)data; POUTPUT->scale = factor; } static void handleName(void* data, wl_output* output, const char* name) { const auto POUTPUT = (COutput*)data; POUTPUT->stringName = std::string{name} + POUTPUT->stringName; POUTPUT->stringPort = std::string{name}; Debug::log(LOG, "output {} name {}", POUTPUT->name, name); } static void handleDescription(void* data, wl_output* output, const char* description) { const auto POUTPUT = (COutput*)data; POUTPUT->stringDesc = description ? std::string{description} : ""; Debug::log(LOG, "output {} description {}", POUTPUT->name, POUTPUT->stringDesc); } static const wl_output_listener outputListener = { .geometry = handleGeometry, .mode = handleMode, .done = handleDone, .scale = handleScale, .name = handleName, .description = handleDescription, }; COutput::COutput(wl_output* output, uint32_t name) : name(name), output(output) { wl_output_add_listener(output, &outputListener, this); } 07070100000020000081A4000000000000000000000001669CF85C00000329000000000000000000000000000000000000002300000000hyprlock-0.4.1/src/core/Output.hpp#pragma once #include <wayland-client.h> #include "../helpers/Vector2D.hpp" #include "LockSurface.hpp" #include <memory> class COutput { public: COutput(wl_output* output, uint32_t name); uint32_t name = 0; bool focused = false; wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; Vector2D size; int scale = 1; std::string stringName = ""; std::string stringPort = ""; std::string stringDesc = ""; std::unique_ptr<CSessionLockSurface> sessionLockSurface; wl_output* output = nullptr; private: }; 07070100000021000081A4000000000000000000000001669CF85C00000302000000000000000000000000000000000000002200000000hyprlock-0.4.1/src/core/Timer.cpp#include "Timer.hpp" CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force) : cb(cb_), data(data_) { expires = std::chrono::system_clock::now() + timeout; allowForceUpdate = force; } bool CTimer::passed() { return std::chrono::system_clock::now() > expires; } void CTimer::cancel() { wasCancelled = true; } bool CTimer::cancelled() { return wasCancelled; } void CTimer::call(std::shared_ptr<CTimer> self) { cb(self, data); } float CTimer::leftMs() { return std::chrono::duration_cast<std::chrono::milliseconds>(expires - std::chrono::system_clock::now()).count(); } bool CTimer::canForceUpdate() { return allowForceUpdate; } 07070100000022000081A4000000000000000000000001669CF85C0000032F000000000000000000000000000000000000002200000000hyprlock-0.4.1/src/core/Timer.hpp#pragma once #include <chrono> #include <functional> class CTimer { public: CTimer(std::chrono::system_clock::duration timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force); void cancel(); bool passed(); bool canForceUpdate(); float leftMs(); bool cancelled(); void call(std::shared_ptr<CTimer> self); private: std::function<void(std::shared_ptr<CTimer> self, void* data)> cb; void* data = nullptr; std::chrono::system_clock::time_point expires; bool wasCancelled = false; bool allowForceUpdate = false; }; 07070100000023000081A4000000000000000000000001669CF85C00009B80000000000000000000000000000000000000002500000000hyprlock-0.4.1/src/core/hyprlock.cpp#include "hyprlock.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" #include "Auth.hpp" #include "Egl.hpp" #include "linux-dmabuf-unstable-v1-protocol.h" #include <sys/wait.h> #include <sys/poll.h> #include <sys/mman.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <xf86drm.h> #include <filesystem> #include <fstream> #include <algorithm> CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender) { m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str()); if (!m_sWaylandState.display) { Debug::log(CRIT, "Couldn't connect to a wayland compositor"); exit(1); } g_pEGL = std::make_unique<CEGL>(m_sWaylandState.display); m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!m_pXKBContext) Debug::log(ERR, "Failed to create xkb context"); if (!immediate) { const auto PGRACE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:grace"); m_tGraceEnds = **PGRACE ? std::chrono::system_clock::now() + std::chrono::seconds(**PGRACE) : std::chrono::system_clock::from_time_t(0); } else { m_tGraceEnds = std::chrono::system_clock::from_time_t(0); } const auto PIMMEDIATERENDER = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:immediate_render"); m_bImmediateRender = immediateRender || **PIMMEDIATERENDER; } CHyprlock::~CHyprlock() { if (dma.gbmDevice) gbm_device_destroy(dma.gbmDevice); if (m_pXKBState) xkb_state_unref(m_pXKBState); if (m_pXKBKeymap) xkb_keymap_unref(m_pXKBKeymap); } // wl_seat static void handleCapabilities(void* data, wl_seat* wl_seat, uint32_t capabilities); static void handleName(void* data, struct wl_seat* wl_seat, const char* name); inline const wl_seat_listener seatListener = { .capabilities = handleCapabilities, .name = handleName, }; // end wl_seat // dmabuf static void handleDMABUFFormat(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format) { ; } static void handleDMABUFModifier(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { g_pHyprlock->dma.dmabufMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo}); } inline const zwp_linux_dmabuf_v1_listener dmabufListener = { .format = handleDMABUFFormat, .modifier = handleDMABUFModifier, }; static void dmabufFeedbackMainDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { Debug::log(LOG, "[core] dmabufFeedbackMainDevice"); RASSERT(!g_pHyprlock->dma.gbm, "double dmabuf feedback"); 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) { Debug::log(WARN, "[dmabuf] unable to open main device?"); exit(1); } g_pHyprlock->dma.gbmDevice = g_pHyprlock->createGBMDevice(drmDev); drmFreeDevice(&drmDev); } static void dmabufFeedbackFormatTable(void* data, zwp_linux_dmabuf_feedback_v1* feedback, int fd, uint32_t size) { Debug::log(TRACE, "[core] dmabufFeedbackFormatTable"); g_pHyprlock->dma.dmabufMods.clear(); g_pHyprlock->dma.formatTable = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (g_pHyprlock->dma.formatTable == MAP_FAILED) { Debug::log(ERR, "[core] format table failed to mmap"); g_pHyprlock->dma.formatTable = nullptr; g_pHyprlock->dma.formatTableSize = 0; return; } g_pHyprlock->dma.formatTableSize = size; } static void dmabufFeedbackDone(void* data, zwp_linux_dmabuf_feedback_v1* feedback) { Debug::log(TRACE, "[core] dmabufFeedbackDone"); if (g_pHyprlock->dma.formatTable) munmap(g_pHyprlock->dma.formatTable, g_pHyprlock->dma.formatTableSize); g_pHyprlock->dma.formatTable = nullptr; g_pHyprlock->dma.formatTableSize = 0; } static void dmabufFeedbackTrancheTargetDevice(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* device_arr) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice"); dev_t device; assert(device_arr->size == sizeof(device)); memcpy(&device, device_arr->data, sizeof(device)); drmDevice* drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) return; if (g_pHyprlock->dma.gbmDevice) { drmDevice* drmDevRenderer = NULL; drmGetDevice2(gbm_device_get_fd(g_pHyprlock->dma.gbmDevice), /* flags */ 0, &drmDevRenderer); g_pHyprlock->dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev); } else { g_pHyprlock->dma.gbmDevice = g_pHyprlock->createGBMDevice(drmDev); g_pHyprlock->dma.deviceUsed = g_pHyprlock->dma.gbm; } } static void dmabufFeedbackTrancheFlags(void* data, zwp_linux_dmabuf_feedback_v1* feedback, uint32_t flags) { ; } static void dmabufFeedbackTrancheFormats(void* data, zwp_linux_dmabuf_feedback_v1* feedback, wl_array* indices) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats"); if (!g_pHyprlock->dma.deviceUsed || !g_pHyprlock->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 assert(sizeof(fm_entry) == 16); uint32_t n_modifiers = g_pHyprlock->dma.formatTableSize / sizeof(fm_entry); fm_entry* fm_entry = (struct fm_entry*)g_pHyprlock->dma.formatTable; uint16_t* idx; for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) { if (*idx >= n_modifiers) continue; Debug::log(TRACE, "GPU Reports supported format {:x} with modifier {:x}", (fm_entry + *idx)->format, (fm_entry + *idx)->modifier); g_pHyprlock->dma.dmabufMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier}); } } static void dmabufFeedbackTrancheDone(void* data, struct zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1) { Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone"); g_pHyprlock->dma.deviceUsed = false; } inline const zwp_linux_dmabuf_feedback_v1_listener dmabufFeedbackListener = { .done = dmabufFeedbackDone, .format_table = dmabufFeedbackFormatTable, .main_device = dmabufFeedbackMainDevice, .tranche_done = dmabufFeedbackTrancheDone, .tranche_target_device = dmabufFeedbackTrancheTargetDevice, .tranche_formats = dmabufFeedbackTrancheFormats, .tranche_flags = dmabufFeedbackTrancheFlags, }; static char* gbm_find_render_node(drmDevice* device) { drmDevice* devices[64]; char* render_node = NULL; int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); for (int i = 0; i < n; ++i) { drmDevice* dev = devices[i]; if (device && !drmDevicesEqual(device, dev)) { continue; } if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) continue; render_node = strdup(dev->nodes[DRM_NODE_RENDER]); break; } drmFreeDevices(devices, n); return render_node; } gbm_device* CHyprlock::createGBMDevice(drmDevice* dev) { char* renderNode = gbm_find_render_node(dev); if (!renderNode) { Debug::log(ERR, "[core] Couldn't find a render node"); return nullptr; } Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode); int fd = open(renderNode, O_RDWR | O_CLOEXEC); if (fd < 0) { Debug::log(ERR, "[core] couldn't open render node"); free(renderNode); return NULL; } free(renderNode); return gbm_create_device(fd); } // end dmabuf // wl_registry static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { g_pHyprlock->onGlobal(data, registry, name, interface, version); } static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) { g_pHyprlock->onGlobalRemoved(data, registry, name); } inline const wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove, }; void CHyprlock::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { const std::string IFACE = interface; Debug::log(LOG, " | got iface: {} v{}", IFACE, version); if (IFACE == ext_session_lock_manager_v1_interface.name) { m_sWaylandState.sessionLock = (ext_session_lock_manager_v1*)wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_seat_interface.name) { if (m_sWaylandState.seat) { Debug::log(WARN, "Hyprlock does not support multi-seat configurations. Only binding to the first seat."); return; } m_sWaylandState.seat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, version); wl_seat_add_listener(m_sWaylandState.seat, &seatListener, nullptr); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_output_interface.name) { m_vOutputs.emplace_back(std::make_unique<COutput>((wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version), name)); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wp_cursor_shape_manager_v1_interface.name) { m_pCursorShape = std::make_unique<CCursorShape>((wp_cursor_shape_manager_v1*)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_compositor_interface.name) { m_sWaylandState.compositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wp_fractional_scale_manager_v1_interface.name) { m_sWaylandState.fractional = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wp_viewporter_interface.name) { m_sWaylandState.viewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == zwp_linux_dmabuf_v1_interface.name) { if (version < 4) { Debug::log(ERR, "cannot use linux_dmabuf with ver < 4"); return; } dma.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); dma.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)dma.linuxDmabuf); zwp_linux_dmabuf_feedback_v1_add_listener((zwp_linux_dmabuf_feedback_v1*)dma.linuxDmabufFeedback, &dmabufFeedbackListener, nullptr); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == zwlr_screencopy_manager_v1_interface.name) { m_sWaylandState.screencopy = (zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } } void CHyprlock::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) { Debug::log(LOG, " | removed iface {}", name); auto outputIt = std::find_if(m_vOutputs.begin(), m_vOutputs.end(), [name](const auto& other) { return other->name == name; }); if (outputIt != m_vOutputs.end()) { g_pRenderer->removeWidgetsFor(outputIt->get()->sessionLockSurface.get()); m_vOutputs.erase(outputIt); } } // end wl_registry 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, NULL); } static void handleUnlockSignal(int sig) { if (sig == SIGUSR1) { Debug::log(LOG, "Unlocking with a SIGUSR1"); g_pHyprlock->releaseSessionLock(); } } static void forceUpdateTimers() { for (auto& t : g_pHyprlock->getTimers()) { if (t->canForceUpdate()) { t->call(t); t->cancel(); } } } static void handleForceUpdateSignal(int sig) { if (sig == SIGUSR2) { forceUpdateTimers(); } } static void handlePollTerminate(int sig) { ; } static void handleCriticalSignal(int sig) { g_pHyprlock->attemptRestoreOnDeath(); // remove our handlers struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGABRT, &sa, NULL); sigaction(SIGSEGV, &sa, NULL); abort(); } void CHyprlock::run() { m_sWaylandState.registry = wl_display_get_registry(m_sWaylandState.display); wl_registry_add_listener(m_sWaylandState.registry, ®istryListener, nullptr); wl_display_roundtrip(m_sWaylandState.display); if (!m_sWaylandState.sessionLock) { Debug::log(CRIT, "Couldn't bind to ext-session-lock-v1, does your compositor support it?"); exit(1); } // gather info about monitors wl_display_roundtrip(m_sWaylandState.display); g_pRenderer = std::make_unique<CRenderer>(); const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP"); const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""}; static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out"); const bool NOFADEOUT = **PNOFADEOUT; Debug::log(LOG, "Running on {}", SZCURRENTD); // Hyprland violates the protocol a bit to allow for this. if (SZCURRENTD != "Hyprland") { while (!g_pRenderer->asyncResourceGatherer->gathered) { wl_display_flush(m_sWaylandState.display); if (wl_display_prepare_read(m_sWaylandState.display) == 0) { wl_display_read_events(m_sWaylandState.display); wl_display_dispatch_pending(m_sWaylandState.display); } else { wl_display_dispatch(m_sWaylandState.display); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } acquireSessionLock(); if (m_bTerminate) // Recieved finished exit(1); g_pAuth = std::make_unique<CAuth>(); g_pAuth->start(); registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART); registerSignalAction(SIGUSR2, handleForceUpdateSignal); registerSignalAction(SIGRTMIN, handlePollTerminate); registerSignalAction(SIGSEGV, handleCriticalSignal); registerSignalAction(SIGABRT, handleCriticalSignal); createSessionLockSurfaces(); pollfd pollfds[] = { { .fd = wl_display_get_fd(m_sWaylandState.display), .events = POLLIN, }, }; std::thread pollThr([this, &pollfds]() { while (!m_bTerminate) { int ret = poll(pollfds, 1, 5000 /* 5 seconds, reasonable. Just in case we need to terminate and the signal fails */); if (ret < 0) { if (errno == EINTR) continue; Debug::log(CRIT, "[core] Polling fds failed with {}", errno); attemptRestoreOnDeath(); m_bTerminate = true; exit(1); } for (size_t i = 0; i < 1; ++i) { if (pollfds[i].revents & POLLHUP) { Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i); attemptRestoreOnDeath(); m_bTerminate = true; exit(1); } } if (ret != 0) { Debug::log(TRACE, "[core] got poll event"); std::lock_guard<std::mutex> lg2(m_sLoopState.eventLoopMutex); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); } } }); 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); if (TIME < least) least = TIME; } 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<std::mutex> lg2(m_sLoopState.eventLoopMutex); Debug::log(TRACE, "timer thread firing"); m_sLoopState.event = true; m_sLoopState.loopCV.notify_all(); } }); m_sLoopState.event = true; // let it process once while (!m_bTerminate) { std::unique_lock lk(m_sLoopState.eventRequestMutex); if (m_sLoopState.event == false) m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; }); if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) { releaseSessionLock(); break; } if (m_bTerminate) break; std::lock_guard<std::mutex> lg(m_sLoopState.eventLoopMutex); m_sLoopState.event = false; if (pollfds[0].revents & POLLIN /* wl */) { Debug::log(TRACE, "got wl event"); wl_display_flush(m_sWaylandState.display); if (wl_display_prepare_read(m_sWaylandState.display) == 0) { wl_display_read_events(m_sWaylandState.display); wl_display_dispatch_pending(m_sWaylandState.display); } else { wl_display_dispatch(m_sWaylandState.display); } } // finalize wayland dispatching. Dispatch pending on the queue int ret = 0; do { ret = wl_display_dispatch_pending(m_sWaylandState.display); wl_display_flush(m_sWaylandState.display); } while (ret > 0 && !m_bTerminate); // do timers m_sLoopState.timersMutex.lock(); auto timerscpy = m_vTimers; m_sLoopState.timersMutex.unlock(); std::vector<std::shared_ptr<CTimer>> 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(); if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) { releaseSessionLock(); break; } } m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); g_pRenderer->asyncResourceGatherer->notify(); g_pRenderer->asyncResourceGatherer->await(); m_vOutputs.clear(); g_pEGL.reset(); g_pRenderer = nullptr; xkb_context_unref(m_pXKBContext); wl_display_disconnect(m_sWaylandState.display); pthread_kill(pollThr.native_handle(), SIGRTMIN); g_pAuth->terminate(); // wait for threads to exit cleanly to avoid a coredump pollThr.join(); timersThr.join(); Debug::log(LOG, "Reached the end, exiting"); } void CHyprlock::unlock() { static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out"); const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP"); const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""}; if (**PNOFADEOUT || SZCURRENTD != "Hyprland") { releaseSessionLock(); return; } m_tFadeEnds = std::chrono::system_clock::now() + std::chrono::milliseconds(500); m_bFadeStarted = true; renderAllOutputs(); } bool CHyprlock::isUnlocked() { return m_bFadeStarted || m_bTerminate; } // wl_seat static void handlePointerEnter(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { if (!g_pHyprlock->m_pCursorShape) return; static auto* const PHIDE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:hide_cursor"); if (**PHIDE) g_pHyprlock->m_pCursorShape->hideCursor(serial); else g_pHyprlock->m_pCursorShape->setShape(serial, wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER); g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}; } static void handlePointerLeave(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) { ; } static void handlePointerAxis(void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { // ignored } static void handlePointerMotion(void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds) return; if (!g_pHyprlock->isUnlocked() && g_pHyprlock->m_vLastEnterCoords.distance({wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}) > 5) { Debug::log(LOG, "In grace and cursor moved more than 5px, unlocking!"); g_pHyprlock->unlock(); } } static void handlePointerButton(void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) { ; } static void handleFrame(void* data, struct wl_pointer* wl_pointer) { ; } static void handleAxisSource(void* data, struct wl_pointer* wl_pointer, uint32_t axis_source) { ; } static void handleAxisStop(void* data, struct wl_pointer* wl_pointer, uint32_t time, uint32_t axis) { ; } static void handleAxisDiscrete(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) { ; } static void handleAxisValue120(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { ; } static void handleAxisRelativeDirection(void* data, struct wl_pointer* wl_pointer, uint32_t axis, uint32_t direction) { ; } inline const wl_pointer_listener pointerListener = { .enter = handlePointerEnter, .leave = handlePointerLeave, .motion = handlePointerMotion, .button = handlePointerButton, .axis = handlePointerAxis, .frame = handleFrame, .axis_source = handleAxisSource, .axis_stop = handleAxisStop, .axis_discrete = handleAxisDiscrete, .axis_value120 = handleAxisValue120, .axis_relative_direction = handleAxisRelativeDirection, }; static void handleKeyboardKeymap(void* data, wl_keyboard* wl_keyboard, uint format, int fd, uint size) { if (!g_pHyprlock->m_pXKBContext) return; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { Debug::log(ERR, "Could not recognise keymap format"); return; } const char* buf = (const char*)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { Debug::log(ERR, "Failed to mmap xkb keymap: {}", errno); return; } g_pHyprlock->m_pXKBKeymap = xkb_keymap_new_from_buffer(g_pHyprlock->m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap((void*)buf, size); close(fd); if (!g_pHyprlock->m_pXKBKeymap) { Debug::log(ERR, "Failed to compile xkb keymap"); return; } g_pHyprlock->m_pXKBState = xkb_state_new(g_pHyprlock->m_pXKBKeymap); if (!g_pHyprlock->m_pXKBState) { Debug::log(ERR, "Failed to create xkb state"); return; } } static void handleKeyboardKey(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { g_pHyprlock->onKey(key, state == WL_KEYBOARD_KEY_STATE_PRESSED); } static void handleKeyboardEnter(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface, wl_array* keys) { ; } static void handleKeyboardLeave(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface) { ; } static void handleKeyboardModifiers(void* data, wl_keyboard* wl_keyboard, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group) { if (!g_pHyprlock->m_pXKBState) return; if (group != g_pHyprlock->m_uiActiveLayout) { g_pHyprlock->m_uiActiveLayout = group; forceUpdateTimers(); } xkb_state_update_mask(g_pHyprlock->m_pXKBState, mods_depressed, mods_latched, mods_locked, 0, 0, group); } static void handleRepeatInfo(void* data, struct wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) { g_pHyprlock->m_iKeebRepeatRate = rate; g_pHyprlock->m_iKeebRepeatDelay = delay; } inline const wl_keyboard_listener keyboardListener = { .keymap = handleKeyboardKeymap, .enter = handleKeyboardEnter, .leave = handleKeyboardLeave, .key = handleKeyboardKey, .modifiers = handleKeyboardModifiers, .repeat_info = handleRepeatInfo, }; static void handleCapabilities(void* data, wl_seat* wl_seat, uint32_t capabilities) { if (capabilities & WL_SEAT_CAPABILITY_POINTER) { g_pHyprlock->m_pPointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(g_pHyprlock->m_pPointer, &pointerListener, wl_seat); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { g_pHyprlock->m_pKeeb = wl_seat_get_keyboard(wl_seat); wl_keyboard_add_listener(g_pHyprlock->m_pKeeb, &keyboardListener, wl_seat); } } static void handleName(void* data, struct wl_seat* wl_seat, const char* name) { ; } // end wl_seat // session_lock static void handleLocked(void* data, ext_session_lock_v1* ext_session_lock_v1) { g_pHyprlock->onLockLocked(); } static void handleFinished(void* data, ext_session_lock_v1* ext_session_lock_v1) { g_pHyprlock->onLockFinished(); } static const ext_session_lock_v1_listener sessionLockListener = { .locked = handleLocked, .finished = handleFinished, }; // end session_lock void CHyprlock::onPasswordCheckTimer() { // check result if (g_pAuth->isAuthenticated()) { unlock(); } else { m_sPasswordState.passBuffer = ""; m_sPasswordState.failedAttempts += 1; Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts); g_pAuth->m_bDisplayFailText = true; forceUpdateTimers(); g_pAuth->start(); renderAllOutputs(); } } void CHyprlock::clearPasswordBuffer() { if (m_sPasswordState.passBuffer.empty()) return; m_sPasswordState.passBuffer = ""; renderAllOutputs(); } void CHyprlock::renderOutput(const std::string& stringPort) { const auto MON = std::find_if(m_vOutputs.begin(), m_vOutputs.end(), [stringPort](const auto& other) { return other->stringPort == stringPort; }); if (MON == m_vOutputs.end() || !MON->get()) return; const auto PMONITOR = MON->get(); if (!PMONITOR->sessionLockSurface) return; PMONITOR->sessionLockSurface->render(); } void CHyprlock::renderAllOutputs() { for (auto& o : m_vOutputs) { if (!o->sessionLockSurface) continue; o->sessionLockSurface->render(); } } void CHyprlock::startKeyRepeat(xkb_keysym_t sym) { if (m_pKeyRepeatTimer) { m_pKeyRepeatTimer->cancel(); m_pKeyRepeatTimer.reset(); } if (m_iKeebRepeatDelay <= 0) return; m_pKeyRepeatTimer = addTimer( std::chrono::milliseconds(m_iKeebRepeatDelay), [sym](std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr); } void CHyprlock::repeatKey(xkb_keysym_t sym) { if (m_iKeebRepeatRate <= 0) return; handleKeySym(sym); // 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](std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr); renderAllOutputs(); } void CHyprlock::onKey(uint32_t key, bool down) { if (m_bFadeStarted || m_bTerminate) return; if (down && std::chrono::system_clock::now() < m_tGraceEnds) { unlock(); return; } if (down && std::find(m_vPressedKeys.begin(), m_vPressedKeys.end(), key) != m_vPressedKeys.end()) { Debug::log(ERR, "Invalid key down event (key already pressed?)"); return; } else if (!down && std::find(m_vPressedKeys.begin(), m_vPressedKeys.end(), key) == m_vPressedKeys.end()) { Debug::log(ERR, "Invalid key down event (stray release event?)"); return; } if (down) m_vPressedKeys.push_back(key); else { std::erase(m_vPressedKeys, key); if (m_pKeyRepeatTimer) { m_pKeyRepeatTimer->cancel(); m_pKeyRepeatTimer.reset(); } } if (g_pAuth->checkWaiting()) { renderAllOutputs(); return; } if (down) { const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8); m_bCapsLock = xkb_state_mod_name_is_active(g_pHyprlock->m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); m_bNumLock = xkb_state_mod_name_is_active(g_pHyprlock->m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED); m_bCtrl = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); handleKeySym(SYM); if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) // keys allowed to repeat startKeyRepeat(SYM); } renderAllOutputs(); } void CHyprlock::handleKeySym(xkb_keysym_t sym) { const auto SYM = sym; if (SYM == XKB_KEY_Escape || (m_bCtrl && (SYM == XKB_KEY_u || SYM == XKB_KEY_BackSpace))) { Debug::log(LOG, "Clearing password buffer"); m_sPasswordState.passBuffer = ""; } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { Debug::log(LOG, "Authenticating"); static auto* const PIGNOREEMPTY = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:ignore_empty_input"); if (m_sPasswordState.passBuffer.empty() && **PIGNOREEMPTY) { Debug::log(LOG, "Ignoring empty input"); return; } g_pAuth->submitInput(m_sPasswordState.passBuffer); } else if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) { if (m_sPasswordState.passBuffer.length() > 0) { // handle utf-8 while ((m_sPasswordState.passBuffer.back() & 0xc0) == 0x80) m_sPasswordState.passBuffer.pop_back(); m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1); } } else if (SYM == XKB_KEY_Caps_Lock) { m_bCapsLock = !m_bCapsLock; } else if (SYM == XKB_KEY_Num_Lock) { m_bNumLock = !m_bNumLock; } else { char buf[16] = {0}; int len = xkb_keysym_to_utf8(SYM, buf, 16); if (len > 1) m_sPasswordState.passBuffer += std::string{buf, len - 1}; } } void CHyprlock::acquireSessionLock() { Debug::log(LOG, "Locking session"); m_sLockState.lock = ext_session_lock_manager_v1_lock(m_sWaylandState.sessionLock); ext_session_lock_v1_add_listener(m_sLockState.lock, &sessionLockListener, nullptr); // roundtrip in case the compositor sends `finished` right away wl_display_roundtrip(m_sWaylandState.display); } void CHyprlock::releaseSessionLock() { Debug::log(LOG, "Unlocking session"); if (m_bTerminate) { Debug::log(ERR, "Unlock already happend?"); return; } if (!m_sLockState.lock) { Debug::log(ERR, "Unlock without a lock object!"); return; } if (!m_bLocked) { // Would be a protocol error to allow this Debug::log(ERR, "Trying to unlock the session, but never recieved the locked event!"); return; } ext_session_lock_v1_unlock_and_destroy(m_sLockState.lock); m_sLockState.lock = nullptr; Debug::log(LOG, "Unlocked, exiting!"); m_bTerminate = true; m_bLocked = false; wl_display_roundtrip(m_sWaylandState.display); } void CHyprlock::createSessionLockSurfaces() { for (auto& o : m_vOutputs) { o->sessionLockSurface = std::make_unique<CSessionLockSurface>(o.get()); } } void CHyprlock::onLockLocked() { Debug::log(LOG, "onLockLocked called"); m_bLocked = true; } void CHyprlock::onLockFinished() { Debug::log(LOG, "onLockFinished called. Seems we got yeeten. Is another lockscreen running?"); if (!m_sLockState.lock) { Debug::log(ERR, "onLockFinished without a lock object!"); return; } if (m_bLocked) // The `finished` event specifies that whenever the `locked` event has been recieved and the compositor sends `finished`, // `unlock_and_destroy` should be called by the client. // This does not mean the session gets unlocked! That is ultimatly the responsiblity of the compositor. ext_session_lock_v1_unlock_and_destroy(m_sLockState.lock); else ext_session_lock_v1_destroy(m_sLockState.lock); m_sLockState.lock = nullptr; m_bTerminate = true; } ext_session_lock_manager_v1* CHyprlock::getSessionLockMgr() { return m_sWaylandState.sessionLock; } ext_session_lock_v1* CHyprlock::getSessionLock() { return m_sLockState.lock; } wl_compositor* CHyprlock::getCompositor() { return m_sWaylandState.compositor; } wl_display* CHyprlock::getDisplay() { return m_sWaylandState.display; } wp_fractional_scale_manager_v1* CHyprlock::getFractionalMgr() { return m_sWaylandState.fractional; } wp_viewporter* 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; }); } size_t CHyprlock::getPasswordFailedAttempts() { return m_sPasswordState.failedAttempts; } std::shared_ptr<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data, bool force) { std::lock_guard<std::mutex> lg(m_sLoopState.timersMutex); const auto T = m_vTimers.emplace_back(std::make_shared<CTimer>(timeout, cb_, data, force)); m_sLoopState.timerEvent = true; m_sLoopState.timerCV.notify_all(); return T; } std::vector<std::shared_ptr<CTimer>> CHyprlock::getTimers() { return m_vTimers; } void CHyprlock::enqueueForceUpdateTimers() { addTimer( std::chrono::milliseconds(1), [](std::shared_ptr<CTimer> self, void* data) { forceUpdateTimers(); }, nullptr, false); } void CHyprlock::spawnAsync(const std::string& args) { Debug::log(LOG, "Executing (async) {}", args); int socket[2]; if (pipe(socket) != 0) Debug::log(LOG, "Unable to create pipe for fork"); pid_t child, grandchild; child = fork(); if (child < 0) { close(socket[0]); close(socket[1]); Debug::log(LOG, "Fail to create the first fork"); return; } if (child == 0) { // run in child sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); grandchild = fork(); if (grandchild == 0) { // run in grandchild close(socket[0]); close(socket[1]); execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); // exit grandchild _exit(0); } close(socket[0]); write(socket[1], &grandchild, sizeof(grandchild)); close(socket[1]); // exit child _exit(0); } // run in parent close(socket[1]); read(socket[0], &grandchild, sizeof(grandchild)); close(socket[0]); // clear child and leave child to init waitpid(child, NULL, 0); if (child < 0) { Debug::log(LOG, "Failed to create the second fork"); return; } Debug::log(LOG, "Process Created with pid {}", grandchild); } std::string CHyprlock::spawnSync(const std::string& cmd) { std::array<char, 128> buffer; std::string result; const std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose); if (!pipe) return ""; while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } return result; } zwlr_screencopy_manager_v1* CHyprlock::getScreencopy() { return m_sWaylandState.screencopy; } void CHyprlock::attemptRestoreOnDeath() { if (m_bTerminate) return; const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR"); const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); if (!XDG_RUNTIME_DIR || !HIS) return; // dirty hack uint64_t timeNowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(0)).count(); const auto LASTRESTARTPATH = std::string{XDG_RUNTIME_DIR} + "/.hyprlockrestart"; if (std::filesystem::exists(LASTRESTARTPATH)) { std::ifstream ifs(LASTRESTARTPATH); std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); uint64_t timeEncoded = 0; try { timeEncoded = std::stoull(content); } catch (std::exception& e) { // oops? ifs.close(); std::filesystem::remove(LASTRESTARTPATH); return; } ifs.close(); if (timeNowMs - timeEncoded < 4000 /* 4s, seems reasonable */) { Debug::log(LOG, "Not restoring on death; less than 4s since last death"); return; } } std::ofstream ofs(LASTRESTARTPATH, std::ios::trunc); ofs << timeNowMs; ofs.close(); spawnSync("hyprctl keyword misc:allow_session_lock_restore true"); spawnAsync("sleep 2 && hyprlock --immediate --immediate-render & disown"); } 07070100000024000081A4000000000000000000000001669CF85C000018BF000000000000000000000000000000000000002500000000hyprlock-0.4.1/src/core/hyprlock.hpp#pragma once #include <wayland-client.h> #include "ext-session-lock-v1-protocol.h" #include "fractional-scale-v1-protocol.h" #include "wlr-screencopy-unstable-v1-protocol.h" #include "viewporter-protocol.h" #include "Output.hpp" #include "CursorShape.hpp" #include "Timer.hpp" #include <memory> #include <vector> #include <condition_variable> #include <optional> #include <xkbcommon/xkbcommon.h> #include <gbm.h> #include <xf86drm.h> struct SDMABUFModifier { uint32_t fourcc = 0; uint64_t mod = 0; }; class CHyprlock { public: CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender); ~CHyprlock(); void run(); void unlock(); bool isUnlocked(); void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name); std::shared_ptr<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data, bool force = false); void enqueueForceUpdateTimers(); void onLockLocked(); void onLockFinished(); void acquireSessionLock(); void releaseSessionLock(); void createSessionLockSurfaces(); void attemptRestoreOnDeath(); void spawnAsync(const std::string& cmd); std::string spawnSync(const std::string& cmd); void onKey(uint32_t key, bool down); void handleKeySym(xkb_keysym_t sym); void startKeyRepeat(xkb_keysym_t sym); void repeatKey(xkb_keysym_t sym); void onPasswordCheckTimer(); void clearPasswordBuffer(); bool passwordCheckWaiting(); std::optional<std::string> passwordLastFailReason(); void renderOutput(const std::string& stringPort); void renderAllOutputs(); size_t getPasswordBufferLen(); size_t getPasswordBufferDisplayLen(); size_t getPasswordFailedAttempts(); ext_session_lock_manager_v1* getSessionLockMgr(); ext_session_lock_v1* getSessionLock(); wl_compositor* getCompositor(); wl_display* getDisplay(); wp_fractional_scale_manager_v1* getFractionalMgr(); wp_viewporter* getViewporter(); zwlr_screencopy_manager_v1* getScreencopy(); wl_pointer* m_pPointer = nullptr; wl_keyboard* m_pKeeb = nullptr; std::unique_ptr<CCursorShape> m_pCursorShape; xkb_context* m_pXKBContext = nullptr; xkb_keymap* m_pXKBKeymap = nullptr; xkb_state* m_pXKBState = nullptr; int32_t m_iKeebRepeatRate = 25; int32_t m_iKeebRepeatDelay = 600; xkb_layout_index_t m_uiActiveLayout = 0; bool m_bTerminate = false; bool m_bLocked = false; bool m_bCapsLock = false; bool m_bNumLock = false; bool m_bCtrl = false; bool m_bFadeStarted = false; bool m_bImmediateRender = false; // std::chrono::system_clock::time_point m_tGraceEnds; std::chrono::system_clock::time_point m_tFadeEnds; Vector2D m_vLastEnterCoords = {}; std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr; std::vector<std::unique_ptr<COutput>> m_vOutputs; std::vector<std::shared_ptr<CTimer>> getTimers(); struct { void* linuxDmabuf = nullptr; void* linuxDmabufFeedback = nullptr; gbm_bo* gbm = nullptr; gbm_device* gbmDevice = nullptr; void* formatTable = nullptr; size_t formatTableSize = 0; bool deviceUsed = false; std::vector<SDMABUFModifier> dmabufMods; } dma; gbm_device* createGBMDevice(drmDevice* dev); private: struct { wl_display* display = nullptr; wl_registry* registry = nullptr; wl_seat* seat = nullptr; ext_session_lock_manager_v1* sessionLock = nullptr; wl_compositor* compositor = nullptr; wp_fractional_scale_manager_v1* fractional = nullptr; wp_viewporter* viewporter = nullptr; zwlr_screencopy_manager_v1* screencopy = nullptr; } m_sWaylandState; struct { ext_session_lock_v1* 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 timerCV; std::mutex timerRequestMutex; bool timerEvent = false; } m_sLoopState; std::vector<std::shared_ptr<CTimer>> m_vTimers; std::vector<uint32_t> m_vPressedKeys; }; inline std::unique_ptr<CHyprlock> g_pHyprlock; 07070100000025000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001B00000000hyprlock-0.4.1/src/helpers07070100000026000081A4000000000000000000000001669CF85C00000774000000000000000000000000000000000000002300000000hyprlock-0.4.1/src/helpers/Box.cpp#include "Box.hpp" #include <cmath> #include <limits> #include <algorithm> #define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x <= (x2) && (vec).y >= (y1) && (vec).y <= (y2)) CBox& CBox::scale(double scale) { x *= scale; y *= scale; w *= scale; h *= scale; return *this; } CBox& CBox::scale(const Vector2D& scale) { x *= scale.x; y *= scale.y; w *= scale.x; h *= scale.y; return *this; } CBox& CBox::translate(const Vector2D& vec) { x += vec.x; y += vec.y; return *this; } Vector2D CBox::middle() const { return Vector2D{x + w / 2.0, y + h / 2.0}; } bool CBox::containsPoint(const Vector2D& vec) const { return VECINRECT(vec, x, y, x + w, y + h); } bool CBox::empty() const { return w == 0 || h == 0; } CBox& CBox::round() { float newW = x + w - std::round(x); float newH = y + h - std::round(y); x = std::round(x); y = std::round(y); w = std::round(newW); h = std::round(newH); return *this; } CBox& CBox::scaleFromCenter(double scale) { double oldW = w, oldH = h; w *= scale; h *= scale; x -= (w - oldW) / 2.0; y -= (h - oldH) / 2.0; return *this; } CBox& CBox::expand(const double& value) { x -= value; y -= value; w += value * 2.0; h += value * 2.0; return *this; } CBox& CBox::noNegativeSize() { std::clamp(w, 0.0, std::numeric_limits<double>::infinity()); std::clamp(h, 0.0, std::numeric_limits<double>::infinity()); return *this; } CBox CBox::roundInternal() { float newW = x + w - std::floor(x); float newH = y + h - std::floor(y); return CBox{std::floor(x), std::floor(y), std::floor(newW), std::floor(newH)}; } CBox CBox::copy() const { return CBox{*this}; } Vector2D CBox::pos() const { return {x, y}; } Vector2D CBox::size() const { return {w, h}; }07070100000027000081A4000000000000000000000001669CF85C000004FE000000000000000000000000000000000000002300000000hyprlock-0.4.1/src/helpers/Box.hpp#pragma once #include "Vector2D.hpp" class CBox { public: CBox(double x_, double y_, double w_, double h_) { x = x_; y = y_; w = w_; h = h_; } CBox() { w = 0; h = 0; } CBox(const double d) { x = d; y = d; w = d; h = d; } CBox(const Vector2D& pos, const Vector2D& size) { x = pos.x; y = pos.y; w = size.x; h = size.y; } CBox& scale(double scale); CBox& scaleFromCenter(double scale); CBox& scale(const Vector2D& scale); CBox& translate(const Vector2D& vec); CBox& round(); CBox& expand(const double& value); CBox& noNegativeSize(); CBox copy() const; Vector2D middle() const; Vector2D pos() const; Vector2D size() const; bool containsPoint(const Vector2D& vec) const; bool empty() const; double x = 0, y = 0; union { double w; double width; }; union { double h; double height; }; double rot = 0; /* rad, ccw */ // bool operator==(const CBox& rhs) const { return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; } private: CBox roundInternal(); }; 07070100000028000081A4000000000000000000000001669CF85C00000281000000000000000000000000000000000000002500000000hyprlock-0.4.1/src/helpers/Color.cpp#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) CColor::CColor() {} CColor::CColor(float r, float g, float b, float a) { this->r = r; this->g = g; this->b = b; this->a = a; } CColor::CColor(uint64_t hex) { this->r = RED(hex); this->g = GREEN(hex); this->b = BLUE(hex); this->a = ALPHA(hex); } uint64_t CColor::getAsHex() { return ((int)a) * 0x1000000 + ((int)r) * 0x10000 + ((int)g) * 0x100 + ((int)b) * 0x1; }07070100000029000081A4000000000000000000000001669CF85C000002E1000000000000000000000000000000000000002500000000hyprlock-0.4.1/src/helpers/Color.hpp#pragma once #include <cstdint> class CColor { public: CColor(); CColor(float r, float g, float b, float a); CColor(uint64_t); float r = 0, g = 0, b = 0, a = 1.f; uint64_t getAsHex(); CColor operator-(const CColor& c2) const { return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a); } CColor operator+(const CColor& c2) const { return CColor(r + c2.r, g + c2.g, b + c2.b, a + c2.a); } CColor operator*(const float& v) const { return CColor(r * v, g * v, b * v, a * v); } bool operator==(const CColor& c2) const { return r == c2.r && g == c2.g && b == c2.b && a == c2.a; } CColor stripA() const { return {r, g, b, 1}; } }; 0707010000002A000081A4000000000000000000000001669CF85C000009D6000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/helpers/Jpeg.cpp#include "Jpeg.hpp" #include "Log.hpp" #include <jpeglib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::filesystem::path& path) { if (!std::filesystem::exists(path)) { Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??"); return nullptr; } if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) { Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy."); return nullptr; } void* imageRawData; struct stat fileInfo = {}; const auto FD = open(path.c_str(), O_RDONLY); fstat(FD, &fileInfo); imageRawData = malloc(fileInfo.st_size); read(FD, imageRawData, fileInfo.st_size); close(FD); // now the JPEG is in the memory jpeg_decompress_struct decompressStruct = {}; jpeg_error_mgr errorManager = {}; decompressStruct.err = jpeg_std_error(&errorManager); jpeg_create_decompress(&decompressStruct); jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size); jpeg_read_header(&decompressStruct, true); #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ decompressStruct.out_color_space = JCS_EXT_BGRA; #else decompressStruct.out_color_space = JCS_EXT_ARGB; #endif // decompress jpeg_start_decompress(&decompressStruct); auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height); if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) { Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)"); return nullptr; } const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface); const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface); JSAMPROW rowRead; while (decompressStruct.output_scanline < decompressStruct.output_height) { const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE); rowRead = PROW; jpeg_read_scanlines(&decompressStruct, &rowRead, 1); } cairo_surface_flush(cairoSurface); cairo_surface_mark_dirty(cairoSurface); cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData); jpeg_finish_decompress(&decompressStruct); jpeg_destroy_decompress(&decompressStruct); return cairoSurface; } 0707010000002B000081A4000000000000000000000001669CF85C0000009C000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/helpers/Jpeg.hpp#pragma once #include <filesystem> #include <cairo/cairo.h> namespace JPEG { cairo_surface_t* createSurfaceFromJPEG(const std::filesystem::path&); }; 0707010000002C000081A4000000000000000000000001669CF85C0000090A000000000000000000000000000000000000002300000000hyprlock-0.4.1/src/helpers/Log.hpp#pragma once #include <format> #include <iostream> #include <string> enum eLogLevel { TRACE = 0, INFO, LOG, WARN, ERR, CRIT, NONE }; #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \ printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \ std::abort(); \ } #define ASSERT(expr) RASSERT(expr, "?") namespace Debug { inline bool quiet = false; inline bool verbose = false; template <typename... Args> void log(eLogLevel level, const std::string& fmt, Args&&... args) { if (!verbose && level == TRACE) return; if (quiet) return; if (level != NONE) { std::cout << '['; switch (level) { case TRACE: std::cout << "TRACE"; break; case INFO: std::cout << "INFO"; break; case LOG: std::cout << "LOG"; break; case WARN: std::cout << "WARN"; break; case ERR: std::cout << "ERR"; break; case CRIT: std::cout << "CRITICAL"; break; default: break; } std::cout << "] "; } std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n"; } };0707010000002D000081A4000000000000000000000001669CF85C00000295000000000000000000000000000000000000002D00000000hyprlock-0.4.1/src/helpers/MiscFunctions.cpp#include <filesystem> #include "MiscFunctions.hpp" 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"); return std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); } // Handling e.g. ./, ../ else if (path.is_relative()) { return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path); } else { return std::filesystem::weakly_canonical(path); } }0707010000002E000081A4000000000000000000000001669CF85C00000064000000000000000000000000000000000000002D00000000hyprlock-0.4.1/src/helpers/MiscFunctions.hpp#pragma once #include <string> std::string absolutePath(const std::string&, const std::string&); 0707010000002F000081A4000000000000000000000001669CF85C0000055B000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/helpers/Vector2D.cpp#include "Vector2D.hpp" #include <algorithm> #include <cmath> Vector2D::Vector2D(double xx, double yy) { x = xx; y = yy; } Vector2D::Vector2D() { x = 0; y = 0; } Vector2D::~Vector2D() {} double Vector2D::normalize() { // get max abs const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y); x /= max; y /= max; return max; } Vector2D Vector2D::floor() const { return Vector2D(std::floor(x), std::floor(y)); } Vector2D Vector2D::round() const { return Vector2D(std::round(x), std::round(y)); } Vector2D Vector2D::clamp(const Vector2D& min, const Vector2D& max) const { return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y)); } double Vector2D::distance(const Vector2D& other) const { double dx = x - other.x; double dy = y - other.y; return std::sqrt(dx * dx + dy * dy); } double Vector2D::size() const { return std::sqrt(x * x + y * y); } Vector2D Vector2D::getComponentMax(const Vector2D& other) const { return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y)); } Vector2D Vector2D::rotated(const double& ang) const { const double COS = std::abs(std::cos(ang)); const double SIN = std::abs(std::sin(ang)); return Vector2D(x * COS + y * SIN, x * SIN + y * COS); } 07070100000030000081A4000000000000000000000001669CF85C00001CD0000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/helpers/Vector2D.hpp#pragma once #include <format> #include <hyprlang.hpp> class Vector2D { public: Vector2D(double, double); Vector2D(); Vector2D(const Hyprlang::VEC2& v) { x = v.x; y = v.y; } ~Vector2D(); double x = 0; double y = 0; // returns the scale double normalize(); Vector2D operator+(const Vector2D& a) const { return Vector2D(this->x + a.x, this->y + a.y); } Vector2D operator-(const Vector2D& a) const { return Vector2D(this->x - a.x, this->y - a.y); } Vector2D operator-() const { return Vector2D(-this->x, -this->y); } Vector2D operator*(const double& a) const { return Vector2D(this->x * a, this->y * a); } Vector2D operator/(const double& a) const { return Vector2D(this->x / a, this->y / a); } bool operator==(const Vector2D& a) const { return a.x == x && a.y == y; } bool operator!=(const Vector2D& a) const { return a.x != x || a.y != y; } Vector2D operator*(const Vector2D& a) const { return Vector2D(this->x * a.x, this->y * a.y); } Vector2D operator/(const Vector2D& a) const { return Vector2D(this->x / a.x, this->y / a.y); } bool operator>(const Vector2D& a) const { return this->x > a.x && this->y > a.y; } bool operator<(const Vector2D& a) const { return this->x < a.x && this->y < a.y; } Vector2D& operator+=(const Vector2D& a) { this->x += a.x; this->y += a.y; return *this; } Vector2D& operator-=(const Vector2D& a) { this->x -= a.x; this->y -= a.y; return *this; } Vector2D& operator*=(const Vector2D& a) { this->x *= a.x; this->y *= a.y; return *this; } Vector2D& operator/=(const Vector2D& a) { this->x /= a.x; this->y /= a.y; return *this; } Vector2D& operator*=(const double& a) { this->x *= a; this->y *= a; return *this; } Vector2D& operator/=(const double& a) { this->x /= a; this->y /= a; return *this; } double distance(const Vector2D& other) const; double size() const; Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const; Vector2D floor() const; Vector2D round() const; Vector2D getComponentMax(const Vector2D& other) const; Vector2D rotated(const double& ang) const; }; /** format specification - 'j', as json array - 'X', same as std::format("{}x{}", vec.x, vec.y) - number, floating point precision, use `0` to format as integer */ // absolutely ridiculous formatter spec parsing #define FORMAT_PARSE(specs__, type__) \ template <typename FormatContext> \ constexpr auto parse(FormatContext& ctx) { \ auto it = ctx.begin(); \ for (; it != ctx.end() && *it != '}'; it++) { \ switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \ } \ return it; \ } #define FORMAT_FLAG(spec__, flag__) \ case spec__: (flag__) = true; break; #define FORMAT_NUMBER(buf__) \ case '0': \ case '1': \ case '2': \ case '3': \ case '4': \ case '5': \ case '6': \ case '7': \ case '8': \ case '9': (buf__).push_back(*it); break; template <typename CharT> struct std::formatter<Vector2D, CharT> : std::formatter<CharT> { bool formatJson = false; bool formatX = false; std::string precision = ""; FORMAT_PARSE(FORMAT_FLAG('j', formatJson) // FORMAT_FLAG('X', formatX) // FORMAT_NUMBER(precision), Vector2D) template <typename FormatContext> auto format(const Vector2D& vec, FormatContext& ctx) const { std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision); if (formatJson) formatString = std::format("[{0}, {0}]", formatString); else if (formatX) formatString = std::format("{0}x{0}", formatString); else formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString); try { string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y)); return std::format_to(ctx.out(), "{}", buf); } catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); } } }; 07070100000031000081A4000000000000000000000001669CF85C00000A40000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/helpers/Webp.cpp#include "Webp.hpp" #include "Log.hpp" #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <webp/decode.h> cairo_surface_t* WEBP::createSurfaceFromWEBP(const std::filesystem::path& path) { if (!std::filesystem::exists(path)) { Debug::log(ERR, "createSurfaceFromWEBP: file doesn't exist??"); return nullptr; } void* imageRawData; struct stat fileInfo = {}; const auto FD = open(path.c_str(), O_RDONLY); fstat(FD, &fileInfo); imageRawData = malloc(fileInfo.st_size); read(FD, imageRawData, fileInfo.st_size); close(FD); // now the WebP is in the memory WebPDecoderConfig config; if (!WebPInitDecoderConfig(&config)) { Debug::log(CRIT, "WebPInitDecoderConfig Failed"); return nullptr; } if (WebPGetFeatures((const unsigned char*)imageRawData, fileInfo.st_size, &config.input) != VP8_STATUS_OK) { Debug::log(ERR, "createSurfaceFromWEBP: file is not webp format"); free(imageRawData); return nullptr; } const auto HEIGHT = config.input.height; const auto WIDTH = config.input.width; auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT); if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) { Debug::log(CRIT, "createSurfaceFromWEBP: Cairo Failed (?)"); cairo_surface_destroy(cairoSurface); return nullptr; } #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ config.output.colorspace = MODE_bgrA; #else config.output.colorspace = MODE_Argb; #endif const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface); const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface); config.options.no_fancy_upsampling = 1; config.output.u.RGBA.rgba = CAIRODATA; config.output.u.RGBA.stride = CAIROSTRIDE; config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT; config.output.is_external_memory = 1; config.output.width = WIDTH; config.output.height = HEIGHT; if (WebPDecode((const unsigned char*)imageRawData, fileInfo.st_size, &config) != VP8_STATUS_OK) { Debug::log(CRIT, "createSurfaceFromWEBP: WebP Decode Failed (?)"); return nullptr; } cairo_surface_flush(cairoSurface); cairo_surface_mark_dirty(cairoSurface); cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_PNG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData); WebPFreeDecBuffer(&config.output); return cairoSurface; } 07070100000032000081A4000000000000000000000001669CF85C0000009C000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/helpers/Webp.hpp#pragma once #include <filesystem> #include <cairo/cairo.h> namespace WEBP { cairo_surface_t* createSurfaceFromWEBP(const std::filesystem::path&); }; 07070100000033000081A4000000000000000000000001669CF85C00000CCD000000000000000000000000000000000000001C00000000hyprlock-0.4.1/src/main.cpp #include "config/ConfigManager.hpp" #include "core/hyprlock.hpp" #include "src/helpers/Log.hpp" #include <cstddef> #include <iostream> void help() { std::cout << "Usage: hyprlock [options]\n\n" "Options:\n" " -v, --verbose - Enable verbose logging\n" " -q, --quiet - Disable logging\n" " -c FILE, --config FILE - Specify config file to use\n" " --display (display) - Specify the Wayland display to connect to\n" " --immediate - Lock immediately, ignoring any configured grace period\n" " --immediate-render - Do not wait for resources before drawing the background\n" " -h, --help - Show this help message\n"; } std::optional<std::string> parseArg(const std::vector<std::string>& args, const std::string& flag, std::size_t& i) { if (i + 1 < args.size()) { return args[++i]; } else { std::cerr << "Error: Missing value for " << flag << " option.\n"; return std::nullopt; } } int main(int argc, char** argv, char** envp) { std::string configPath; std::string wlDisplay; bool immediate = false; bool immediateRender = false; bool showHelp = false; std::vector<std::string> args(argv, argv + argc); for (std::size_t i = 1; i < args.size(); ++i) { const std::string arg = argv[i]; if (arg == "--verbose" || arg == "-v") Debug::verbose = true; else if (arg == "--quiet" || arg == "-q") Debug::quiet = true; else if ((arg == "--config" || arg == "-c") && i + 1 < (std::size_t)argc) { if (auto value = parseArg(args, arg, i); value) configPath = *value; else return 1; } else if (arg == "--display" && i + 1 < (std::size_t)argc) { if (auto value = parseArg(args, arg, i); value) wlDisplay = *value; else return 1; } else if (arg == "--immediate") immediate = true; else if (arg == "--immediate-render") immediateRender = true; else if (arg == "--help" || arg == "-h") { showHelp = true; break; } else { std::cerr << "Unknown option: " << arg << "\n"; help(); return 1; } } if (showHelp) { help(); return 0; } try { g_pConfigManager = std::make_unique<CConfigManager>(configPath); g_pConfigManager->init(); } catch (const std::exception& ex) { Debug::log(CRIT, "ConfigManager threw: {}", ex.what()); if (std::string(ex.what()).contains("File does not exist")) Debug::log(NONE, " Make sure you have a config."); return 1; } try { g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender); g_pHyprlock->run(); } catch (const std::exception& ex) { Debug::log(CRIT, "Hyprlock threw: {}", ex.what()); return 1; } return 0; } 07070100000034000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000001C00000000hyprlock-0.4.1/src/renderer07070100000035000081A4000000000000000000000001669CF85C00003A0A000000000000000000000000000000000000003600000000hyprlock-0.4.1/src/renderer/AsyncResourceGatherer.cpp#include "AsyncResourceGatherer.hpp" #include "../config/ConfigManager.hpp" #include "../core/Egl.hpp" #include <cairo/cairo.h> #include <magic.h> #include <pango/pangocairo.h> #include <algorithm> #include <filesystem> #include "../core/hyprlock.hpp" #include "../helpers/MiscFunctions.hpp" #include "../helpers/Jpeg.hpp" #include "../helpers/Webp.hpp" #include "src/helpers/Color.hpp" #include "src/helpers/Log.hpp" CAsyncResourceGatherer::CAsyncResourceGatherer() { if (g_pHyprlock->getScreencopy()) enqueueDMAFrames(); initialGatherThread = std::thread([this]() { this->gather(); }); initialGatherThread.detach(); asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); }); asyncLoopThread.detach(); } void CAsyncResourceGatherer::enqueueDMAFrames() { // some things can't be done async :( // gather background textures when needed const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); std::vector<std::string> mons; for (auto& c : CWIDGETS) { if (c.type != "background") continue; if (std::string{std::any_cast<Hyprlang::STRING>(c.values.at("path"))} != "screenshot") continue; // mamma mia if (c.monitor.empty()) { mons.clear(); for (auto& m : g_pHyprlock->m_vOutputs) { mons.push_back(m->stringPort); } break; } else mons.push_back(c.monitor); } for (auto& mon : mons) { const auto MON = std::find_if(g_pHyprlock->m_vOutputs.begin(), g_pHyprlock->m_vOutputs.end(), [mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); }); if (MON == g_pHyprlock->m_vOutputs.end()) continue; const auto PMONITOR = MON->get(); dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR)); } } SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { for (auto& a : assets) { if (a.first == id) return &a.second; } if (apply()) { for (auto& a : assets) { if (a.first == id) return &a.second; } }; for (auto& dma : dmas) { if (id == dma->resourceID) return dma->asset.ready ? &dma->asset : nullptr; } return nullptr; } enum class FileType { PNG, JPEG, WEBP, UNKNOWN, }; FileType getFileType(const std::filesystem::path& path) { std::string ext = path.extension().string(); // convert the extension to lower case std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { return c <= 'Z' && c >= 'A' ? c - ('Z' - 'z') : c; }); FileType ft = FileType::UNKNOWN; Debug::log(TRACE, "Extension: {}", ext); if (ext == ".png") ft = FileType::PNG; else if (ext == ".jpg" || ext == ".jpeg") ft = FileType::JPEG; else if (ext == ".webp") ft = FileType::WEBP; else { // magic is slow, so only use it when no recognized extension is found auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS); magic_load(handle, nullptr); const auto type_str = std::string(magic_file(handle, path.c_str())); const auto first_word = type_str.substr(0, type_str.find(" ")); magic_close(handle); if (first_word == "PNG") ft = FileType::PNG; else if (first_word == "JPEG") ft = FileType::JPEG; else if (first_word == "RIFF" && type_str.find("Web/P image") != std::string::npos) ft = FileType::WEBP; } return ft; } cairo_surface_t* getCairoSurfaceFromImageFile(const std::filesystem::path& path) { cairo_surface_t* cairoSurface = nullptr; switch (getFileType(path)) { case FileType::PNG: cairoSurface = cairo_image_surface_create_from_png(path.c_str()); break; case FileType::JPEG: cairoSurface = JPEG::createSurfaceFromJPEG(path); break; case FileType::WEBP: cairoSurface = WEBP::createSurfaceFromWEBP(path); break; default: Debug::log(ERR, "unrecognized image format of {}", path.c_str()); } return cairoSurface; } void CAsyncResourceGatherer::gather() { const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); g_pEGL->makeCurrent(nullptr); // gather resources to preload // clang-format off int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) { return w.type == "background" || w.type == "image"; }); // clang-format on progress = 0; for (auto& c : CWIDGETS) { if (c.type == "background" || c.type == "image") { #if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100 progress = progress + 1.0 / (preloads + 1.0); #else progress += 1.0 / (preloads + 1.0); #endif std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path")); if (path.empty() || path == "screenshot") continue; std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; // render the image directly, since we are in a seperate thread CAsyncResourceGatherer::SPreloadRequest rq; rq.type = CAsyncResourceGatherer::TARGET_IMAGE; rq.asset = path; rq.id = id; renderImage(rq); } } while (std::any_of(dmas.begin(), dmas.end(), [](const auto& d) { return !d->asset.ready; })) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } gathered = true; } bool CAsyncResourceGatherer::apply() { preloadTargetsMutex.lock(); if (preloadTargets.empty()) { preloadTargetsMutex.unlock(); return false; } auto currentPreloadTargets = preloadTargets; preloadTargets.clear(); preloadTargetsMutex.unlock(); for (auto& t : currentPreloadTargets) { if (t.type == TARGET_IMAGE) { const auto ASSET = &assets[t.id]; const auto SURFACESTATUS = cairo_surface_status((cairo_surface_t*)t.cairosurface); const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS)); ASSET->texture.m_iType = TEXTURE_INVALID; } ASSET->texture.m_vSize = t.size; ASSET->texture.allocate(); glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); } glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data); cairo_destroy((cairo_t*)t.cairo); cairo_surface_destroy((cairo_surface_t*)t.cairosurface); } else { Debug::log(ERR, "Unsupported type in ::apply() {}", (int)t.type); } } return true; } void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; target.id = rq.id; std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, "")); const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH); if (!CAIROISURFACE) { Debug::log(ERR, "No cairo surface!"); return; } const auto CAIRO = cairo_create(CAIROISURFACE); cairo_scale(CAIRO, 1, 1); target.cairo = CAIRO; target.cairosurface = CAIROISURFACE; target.data = cairo_image_surface_get_data(CAIROISURFACE); target.size = {(double)cairo_image_surface_get_width(CAIROISURFACE), (double)cairo_image_surface_get_height(CAIROISURFACE)}; std::lock_guard lg{preloadTargetsMutex}; preloadTargets.push_back(target); } void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; /* text is just an image lol */ target.id = rq.id; const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16; const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CColor>(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0); const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans"; const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false; static auto* const TRIM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:text_trim"); std::string TEXT = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset; if (**TRIM) { TEXT.erase(0, TEXT.find_first_not_of(" \n\r\t")); TEXT.erase(TEXT.find_last_not_of(" \n\r\t") + 1); } auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */); auto CAIRO = cairo_create(CAIROSURFACE); // draw title using Pango PangoLayout* layout = pango_cairo_create_layout(CAIRO); PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str()); pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); if (rq.props.contains("text_align")) { const std::string TEXTALIGN = std::any_cast<std::string>(rq.props.at("text_align")); PangoAlignment align = PANGO_ALIGN_LEFT; if (TEXTALIGN == "center") align = PANGO_ALIGN_CENTER; else if (TEXTALIGN == "right") align = PANGO_ALIGN_RIGHT; pango_layout_set_alignment(layout, align); } PangoAttrList* attrList = nullptr; GError* gError = nullptr; char* buf = nullptr; if (pango_parse_markup(TEXT.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) pango_layout_set_text(layout, buf, -1); else { Debug::log(ERR, "Pango markup parsing for {} failed: {}", TEXT, gError->message); g_error_free(gError); pango_layout_set_text(layout, TEXT.c_str(), -1); } if (!attrList) attrList = pango_attr_list_new(); if (buf) free(buf); pango_attr_list_insert(attrList, pango_attr_scale_new(1)); pango_layout_set_attributes(layout, attrList); pango_attr_list_unref(attrList); int layoutWidth, layoutHeight; pango_layout_get_size(layout, &layoutWidth, &layoutHeight); // TODO: avoid this? cairo_destroy(CAIRO); cairo_surface_destroy(CAIROSURFACE); CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE); CAIRO = cairo_create(CAIROSURFACE); // clear the pixmap cairo_save(CAIRO); cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); cairo_paint(CAIRO); cairo_restore(CAIRO); // render the thing cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a); cairo_move_to(CAIRO, 0, 0); pango_cairo_show_layout(CAIRO, layout); g_object_unref(layout); cairo_surface_flush(CAIROSURFACE); target.cairo = CAIRO; target.cairosurface = CAIROSURFACE; target.data = cairo_image_surface_get_data(CAIROSURFACE); target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE}; std::lock_guard lg{preloadTargetsMutex}; preloadTargets.push_back(target); } struct STimerCallbackData { void (*cb)(void*) = nullptr; void* data = nullptr; }; static void timerCallback(std::shared_ptr<CTimer> self, void* data_) { auto data = (STimerCallbackData*)data_; data->cb(data->data); delete data; } void CAsyncResourceGatherer::asyncAssetSpinLock() { while (!g_pHyprlock->m_bTerminate) { std::unique_lock lk(asyncLoopState.requestsMutex); if (asyncLoopState.pending == false) // avoid a lock if a thread managed to request something already since we .unlock()ed asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events asyncLoopState.pending = false; if (asyncLoopState.requests.empty()) { lk.unlock(); continue; } auto requests = asyncLoopState.requests; asyncLoopState.requests.clear(); lk.unlock(); // process requests for (auto& r : requests) { Debug::log(TRACE, "Processing requested resourceID {}", r.id); if (r.type == TARGET_TEXT) { renderText(r); } else if (r.type == TARGET_IMAGE) { renderImage(r); } else { Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type); continue; } // plant timer for callback if (r.callback) g_pHyprlock->addTimer(std::chrono::milliseconds(0), timerCallback, new STimerCallbackData{r.callback, r.callbackData}); } } } void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) { Debug::log(TRACE, "Requesting label resource {}", request.id); std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex); asyncLoopState.requests.push_back(request); asyncLoopState.pending = true; asyncLoopState.requestsCV.notify_all(); } void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* asset) { std::erase_if(assets, [asset](const auto& a) { return &a.second == asset; }); } void CAsyncResourceGatherer::notify() { std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex); asyncLoopState.requests.clear(); asyncLoopState.pending = true; asyncLoopState.requestsCV.notify_all(); } void CAsyncResourceGatherer::await() { if (initialGatherThread.joinable()) initialGatherThread.join(); if (asyncLoopThread.joinable()) asyncLoopThread.join(); } 07070100000036000081A4000000000000000000000001669CF85C0000093F000000000000000000000000000000000000003600000000hyprlock-0.4.1/src/renderer/AsyncResourceGatherer.hpp#pragma once #include "DMAFrame.hpp" #include <thread> #include <atomic> #include <vector> #include <unordered_map> #include <condition_variable> #include <any> #include "Shared.hpp" class CAsyncResourceGatherer { public: CAsyncResourceGatherer(); std::atomic<bool> gathered = false; std::atomic<float> progress = 0; /* only call from ogl thread */ SPreloadedAsset* getAssetByID(const std::string& id); bool apply(); enum eTargetType { TARGET_IMAGE = 0, TARGET_TEXT }; struct SPreloadRequest { eTargetType type; std::string asset; std::string id; std::unordered_map<std::string, std::any> props; // optional. Callbacks will be dispatched from the main thread, // so wayland/gl calls are OK. // will fire once the resource is fully loaded and ready. void (*callback)(void*) = nullptr; void* callbackData = nullptr; }; void requestAsyncAssetPreload(const SPreloadRequest& request); void unloadAsset(SPreloadedAsset* asset); void notify(); void await(); private: std::thread asyncLoopThread; std::thread initialGatherThread; void asyncAssetSpinLock(); void renderText(const SPreloadRequest& rq); void renderImage(const SPreloadRequest& rq); struct { std::condition_variable requestsCV; std::mutex requestsMutex; std::vector<SPreloadRequest> requests; bool pending = false; bool busy = false; } asyncLoopState; struct SPreloadTarget { eTargetType type = TARGET_IMAGE; std::string id = ""; void* data; void* cairo; void* cairosurface; Vector2D size; }; std::vector<std::unique_ptr<CDMAFrame>> dmas; std::vector<SPreloadTarget> preloadTargets; std::mutex preloadTargetsMutex; std::unordered_map<std::string, SPreloadedAsset> assets; void gather(); void enqueueDMAFrames(); }; 07070100000037000081A4000000000000000000000001669CF85C00002271000000000000000000000000000000000000002900000000hyprlock-0.4.1/src/renderer/DMAFrame.cpp#include "DMAFrame.hpp" #include "linux-dmabuf-unstable-v1-protocol.h" #include "wlr-screencopy-unstable-v1-protocol.h" #include "../helpers/Log.hpp" #include "../core/hyprlock.hpp" #include "../core/Egl.hpp" #include <EGL/eglext.h> #include <libdrm/drm_fourcc.h> #include <GLES3/gl32.h> #include <GLES3/gl3ext.h> #include <GLES2/gl2ext.h> #include <unistd.h> static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; // static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { const auto PDATA = (SScreencopyData*)data; Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PDATA); PDATA->size = stride * height; PDATA->stride = stride; } static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) { ; } static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { const auto PDATA = (SScreencopyData*)data; Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PDATA); if (!PDATA->frame->onBufferReady()) { Debug::log(ERR, "onBufferReady failed"); return; } zwlr_screencopy_frame_v1_destroy(frame); } static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) { ; } static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { ; } static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { const auto PDATA = (SScreencopyData*)data; Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PDATA); PDATA->w = width; PDATA->h = height; PDATA->fmt = format; Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format); } static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) { const auto PDATA = (SScreencopyData*)data; Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PDATA); if (!PDATA->frame->onBufferDone()) { Debug::log(ERR, "onBufferDone failed"); return; } zwlr_screencopy_frame_v1_copy(frame, PDATA->frame->wlBuffer); Debug::log(TRACE, "[sc] wlr frame copied"); } static const zwlr_screencopy_frame_v1_listener wlrFrameListener = { .buffer = wlrOnBuffer, .flags = wlrOnFlags, .ready = wlrOnReady, .failed = wlrOnFailed, .damage = wlrOnDamage, .linux_dmabuf = wlrOnDmabuf, .buffer_done = wlrOnBufferDone, }; std::string CDMAFrame::getResourceId(COutput* output) { return std::format("dma:{}-{}x{}", output->stringPort, output->size.x, output->size.y); } CDMAFrame::CDMAFrame(COutput* output_) { resourceID = getResourceId(output_); if (!glEGLImageTargetTexture2DOES) { glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (!glEGLImageTargetTexture2DOES) { Debug::log(ERR, "No glEGLImageTargetTexture2DOES??"); return; } } if (!eglQueryDmaBufModifiersEXT) eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); // firstly, plant a listener for the frame frameCb = zwlr_screencopy_manager_v1_capture_output(g_pHyprlock->getScreencopy(), false, output_->output); scdata.frame = this; zwlr_screencopy_frame_v1_add_listener(frameCb, &wlrFrameListener, &scdata); } CDMAFrame::~CDMAFrame() { if (g_pEGL) eglDestroyImage(g_pEGL->eglDisplay, image); // leaks bo and stuff but lives throughout so for now who cares } bool CDMAFrame::onBufferDone() { uint32_t flags = GBM_BO_USE_RENDERING; if (!eglQueryDmaBufModifiersEXT) { Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support"); bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags); } else { std::vector<uint64_t> mods; mods.resize(64); std::vector<EGLBoolean> externalOnly; externalOnly.resize(64); int num = 0; if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, scdata.fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) { Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo"); bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags); } else { Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num); std::vector<uint64_t> goodMods; for (int i = 0; i < num; ++i) { if (externalOnly[i]) { Debug::log(TRACE, "Modifier {:x} failed test", mods[i]); continue; } Debug::log(TRACE, "Modifier {:x} passed test", mods[i]); goodMods.push_back(mods[i]); } uint64_t zero = 0; bool hasLinear = std::find(goodMods.begin(), goodMods.end(), 0) != goodMods.end(); bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, hasLinear ? &zero : goodMods.data(), hasLinear ? 1 : goodMods.size(), flags); } } if (!bo) { Debug::log(ERR, "Couldn't create a drm buffer"); return false; } planes = gbm_bo_get_plane_count(bo); uint64_t mod = gbm_bo_get_modifier(bo); Debug::log(LOG, "bo chose modifier {:x}", mod); zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pHyprlock->dma.linuxDmabuf); if (!params) { Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed"); gbm_bo_destroy(bo); return false; } for (size_t plane = 0; plane < (size_t)planes; plane++) { size[plane] = 0; stride[plane] = gbm_bo_get_stride_for_plane(bo, plane); offset[plane] = gbm_bo_get_offset(bo, plane); fd[plane] = gbm_bo_get_fd_for_plane(bo, plane); if (fd[plane] < 0) { Debug::log(ERR, "gbm_bo_get_fd_for_plane failed"); zwp_linux_buffer_params_v1_destroy(params); gbm_bo_destroy(bo); for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) { close(fd[plane_tmp]); } return false; } zwp_linux_buffer_params_v1_add(params, fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff); } wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, scdata.w, scdata.h, scdata.fmt, 0); zwp_linux_buffer_params_v1_destroy(params); if (!wlBuffer) { Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed"); gbm_bo_destroy(bo); for (size_t plane = 0; plane < (size_t)planes; plane++) close(fd[plane]); return false; } return true; } bool CDMAFrame::onBufferReady() { static const int general_attribs = 3; static const int plane_attribs = 5; static const int entries_per_attrib = 2; EGLAttrib attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1]; int attr = 0; Vector2D size{scdata.w, scdata.h}; attribs[attr++] = EGL_WIDTH; attribs[attr++] = size.x; attribs[attr++] = EGL_HEIGHT; attribs[attr++] = size.y; attribs[attr++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[attr++] = scdata.fmt; attribs[attr++] = EGL_DMA_BUF_PLANE0_FD_EXT; attribs[attr++] = fd[0]; attribs[attr++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; attribs[attr++] = offset[0]; attribs[attr++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; attribs[attr++] = stride[0]; attribs[attr] = EGL_NONE; image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (image == EGL_NO_IMAGE) { Debug::log(ERR, "failed creating an egl image"); return false; } asset.texture.allocate(); asset.texture.m_vSize = size; glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); glBindTexture(GL_TEXTURE_2D, 0); Debug::log(LOG, "Got dma frame with size {}", size); asset.ready = true; return true; }07070100000038000081A4000000000000000000000001669CF85C000003A5000000000000000000000000000000000000002900000000hyprlock-0.4.1/src/renderer/DMAFrame.hpp#pragma once #include "../core/Output.hpp" #include <gbm.h> #include "Shared.hpp" struct zwlr_screencopy_frame_v1; class CDMAFrame; struct SScreencopyData { int w = 0, h = 0; uint32_t fmt; size_t size; size_t stride; CDMAFrame* frame = nullptr; }; class CDMAFrame { public: static std::string getResourceId(COutput* output); CDMAFrame(COutput* mon); ~CDMAFrame(); bool onBufferDone(); bool onBufferReady(); wl_buffer* wlBuffer = nullptr; std::string resourceID; SPreloadedAsset asset; private: gbm_bo* bo = nullptr; int planes = 0; int fd[4]; uint32_t size[4], stride[4], offset[4]; zwlr_screencopy_frame_v1* frameCb = nullptr; SScreencopyData scdata; EGLImage image = nullptr; };07070100000039000081A4000000000000000000000001669CF85C00001013000000000000000000000000000000000000002C00000000hyprlock-0.4.1/src/renderer/Framebuffer.cpp#include "Framebuffer.hpp" #include "../helpers/Log.hpp" #include <libdrm/drm_fourcc.h> static uint32_t drmFormatToGL(uint32_t drm) { switch (drm) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; default: return GL_RGBA; } return GL_RGBA; } static uint32_t glFormatToType(uint32_t gl) { return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; } bool CFramebuffer::alloc(int w, int h, bool highres) { bool firstAlloc = false; uint32_t glFormat = highres ? GL_RGBA16F : drmFormatToGL(DRM_FORMAT_XRGB2101010); // TODO: revise only 10b when I find a way to figure out without sc whether display is 10b uint32_t glType = highres ? GL_FLOAT : glFormatToType(glFormat); if (m_iFb == (uint32_t)-1) { firstAlloc = true; glGenFramebuffers(1, &m_iFb); } if (m_cTex.m_iTexID == 0) { firstAlloc = true; glGenTextures(1, &m_cTex.m_iTexID); glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); m_cTex.m_vSize = {w, h}; } if (firstAlloc || m_vSize != Vector2D(w, h)) { glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0); if (m_pStencilTex) { glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); } auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { Debug::log(ERR, "Framebuffer incomplete, couldn't create! (FB status: {})", status); abort(); } Debug::log(LOG, "Framebuffer created, status {}", status); } glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); m_vSize = Vector2D(w, h); return true; } void CFramebuffer::addStencil() { if (!m_pStencilTex) { Debug::log(ERR, "No stencil texture allocated."); return; } glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo! (FB status: {})", status); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } void CFramebuffer::bind() const { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iFb); glViewport(0, 0, m_vSize.x, m_vSize.y); } void CFramebuffer::release() { 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() { release(); } bool CFramebuffer::isAllocated() { return m_iFb != (GLuint)-1; }0707010000003A000081A4000000000000000000000001669CF85C000001C7000000000000000000000000000000000000002C00000000hyprlock-0.4.1/src/renderer/Framebuffer.hpp#pragma once #include "../helpers/Vector2D.hpp" #include <GLES3/gl32.h> #include "Texture.hpp" class CFramebuffer { public: ~CFramebuffer(); bool alloc(int w, int h, bool highres = false); void addStencil(); void bind() const; void release(); void reset(); bool isAllocated(); Vector2D m_vSize; CTexture m_cTex; GLuint m_iFb = -1; CTexture* m_pStencilTex = nullptr; };0707010000003B000081A4000000000000000000000001669CF85C00005343000000000000000000000000000000000000002900000000hyprlock-0.4.1/src/renderer/Renderer.cpp#include "Renderer.hpp" #include "../core/Egl.hpp" #include "../config/ConfigManager.hpp" #include "../helpers/Color.hpp" #include "../core/Output.hpp" #include "../core/hyprlock.hpp" #include "../renderer/DMAFrame.hpp" #include "mtx.hpp" #include <GLES3/gl32.h> #include <GLES3/gl3ext.h> #include <algorithm> #include "Shaders.hpp" #include "src/helpers/Log.hpp" #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 }; GLuint compileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); return shader; } 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.c_str()); auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag); RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag.c_str()); auto prog = glCreateProgram(); glAttachShader(prog, vertCompiled); glAttachShader(prog, fragCompiled); glLinkProgram(prog); glDetachShader(prog, vertCompiled); glDetachShader(prog, fragCompiled); glDeleteShader(vertCompiled); glDeleteShader(fragCompiled); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); return prog; } static void glMessageCallbackA(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { if (type != GL_DEBUG_TYPE_ERROR) return; Debug::log(LOG, "[gl] {}", (const char*)message); } CRenderer::CRenderer() { g_pEGL->makeCurrent(nullptr); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(glMessageCallbackA, 0); 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, 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"); wlr_matrix_identity(projMatrix.data()); asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>(); } static int frames = 0; static bool firstFullFrame = false; // CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) { static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar"); static auto* const PNOFADEIN = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_in"); static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out"); matrixProjection(projection.data(), surf.size.x, surf.size.y, WL_OUTPUT_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; float bga = 0.0; const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered; if (WAITFORASSETS) { // render status if (!**PDISABLEBAR) { CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2}; renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0); } } else { if (!firstFullFrame) { firstFullFrameTime = std::chrono::system_clock::now(); firstFullFrame = true; } bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - firstFullFrameTime).count() / 500000.0, 0.0, 1.0); if (**PNOFADEIN) bga = 1.0; if (g_pHyprlock->m_bFadeStarted && !**PNOFADEOUT) { bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(g_pHyprlock->m_tFadeEnds - std::chrono::system_clock::now()).count() / 500000.0 - 0.02, 0.0, 1.0); // - 0.02 so that the fade ends a little earlier than the final second } // render widgets const auto WIDGETS = getOrCreateWidgetsFor(&surf); for (auto& w : *WIDGETS) { feedback.needsFrame = w->draw({bga}) || feedback.needsFrame; } } frames++; Debug::log(TRACE, "frame {}", frames); feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered || bga < 1.0; glDisable(GL_BLEND); return feedback; } void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) { float matrix[9]; wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, box.rot, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, projection.data(), matrix); glUseProgram(rectShader.program); glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix); // 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(box.x, box.y); const auto FULLSIZE = Vector2D(box.width, box.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::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional<wl_output_transform> tr) { float matrix[9]; wlr_matrix_project_box(matrix, &box, tr.value_or(WL_OUTPUT_TRANSFORM_FLIPPED_180) /* ugh coordinate spaces */, box.rot, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, projection.data(), matrix); CShader* shader = &texShader; glActiveTexture(GL_TEXTURE0); glBindTexture(tex.m_iTarget, tex.m_iTexID); glUseProgram(shader->program); glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); glUniform1i(shader->tex, 0); glUniform1f(shader->alpha, a); const auto TOPLEFT = Vector2D(box.x, box.y); const auto FULLSIZE = Vector2D(box.width, box.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); } std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) { if (!widgets.contains(surf)) { auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); std::sort(CWIDGETS.begin(), CWIDGETS.end(), [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) { return std::any_cast<Hyprlang::INT>(a.values.at("zindex")) < std::any_cast<Hyprlang::INT>(b.values.at("zindex")); }); for (auto& c : CWIDGETS) { if (!c.monitor.empty() && c.monitor != surf->output->stringPort && !surf->output->stringDesc.starts_with(c.monitor)) continue; // by type if (c.type == "background") { const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path")); std::string resourceID = ""; if (PATH == "screenshot") { resourceID = CDMAFrame::getResourceId(surf->output); // When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available. // Dynamic ones are tricky, because a screencopy would copy hyprlock itself. if (asyncResourceGatherer->gathered) { if (!asyncResourceGatherer->getAssetByID(resourceID)) resourceID = ""; // Fallback to solid color (background:color) } if (!g_pHyprlock->getScreencopy()) { Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); resourceID = ""; } } else if (!PATH.empty()) resourceID = "background:" + PATH; widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, surf->output, resourceID, c.values, PATH == "screenshot")); } else if (c.type == "input-field") { widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(surf->size, c.values, surf->output->stringPort)); } else if (c.type == "label") { widgets[surf].emplace_back(std::make_unique<CLabel>(surf->size, c.values, surf->output->stringPort)); } else if (c.type == "shape") { widgets[surf].emplace_back(std::make_unique<CShape>(surf->size, c.values)); } else if (c.type == "image") { const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path")); std::string resourceID = ""; if (!PATH.empty()) resourceID = "image:" + PATH; widgets[surf].emplace_back(std::make_unique<CImage>(surf->size, surf->output, resourceID, c.values)); } } } return &widgets[surf]; } void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) { glDisable(GL_BLEND); glDisable(GL_STENCIL_TEST); float matrix[9]; CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y}; wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, projection.data(), 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); 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); 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); 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, WL_OUTPUT_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(const CSessionLockSurface* surf) { widgets.erase(surf); } 0707010000003C000081A4000000000000000000000001669CF85C0000091A000000000000000000000000000000000000002900000000hyprlock-0.4.1/src/renderer/Renderer.hpp#pragma once #include <memory> #include <chrono> #include <optional> #include "../core/LockSurface.hpp" #include "Shader.hpp" #include "../helpers/Box.hpp" #include "../helpers/Color.hpp" #include "AsyncResourceGatherer.hpp" #include "widgets/IWidget.hpp" #include "Framebuffer.hpp" typedef std::unordered_map<const CSessionLockSurface*, std::vector<std::unique_ptr<IWidget>>> 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<CColor> colorize; float boostA = 1.0; }; SRenderFeedback renderLock(const CSessionLockSurface& surface); void renderRect(const CBox& box, const CColor& col, int rounding = 0); void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional<wl_output_transform> tr = {}); void blurFB(const CFramebuffer& outfb, SBlurParams params); std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer; std::chrono::system_clock::time_point firstFullFrameTime; void pushFb(GLint fb); void popFb(); void removeWidgetsFor(const CSessionLockSurface* surf); private: widgetMap_t widgets; std::vector<std::unique_ptr<IWidget>>* getOrCreateWidgetsFor(const CSessionLockSurface* surf); CShader rectShader; CShader texShader; CShader blurShader1; CShader blurShader2; CShader blurPrepareShader; CShader blurFinishShader; std::array<float, 9> projMatrix; std::array<float, 9> projection; std::vector<GLint> boundFBs; }; inline std::unique_ptr<CRenderer> g_pRenderer;0707010000003D000081A4000000000000000000000001669CF85C000001CB000000000000000000000000000000000000002700000000hyprlock-0.4.1/src/renderer/Shader.cpp#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; }0707010000003E000081A4000000000000000000000001669CF85C000006B2000000000000000000000000000000000000002700000000hyprlock-0.4.1/src/renderer/Shader.hpp#pragma once #include <unordered_map> #include <GLES3/gl32.h> #include <string> class CShader { public: ~CShader(); GLuint program = 0; GLint proj = -1; GLint color = -1; GLint alphaMatte = -1; GLint tex = -1; GLint alpha = -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 angle = -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<std::string, GLint> m_muUniforms; };0707010000003F000081A4000000000000000000000001669CF85C00002521000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/renderer/Shaders.hpp#pragma once #include <string> 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 if (pixCoord.x + pixCoord.y > radius) { float dist = length(pixCoord); if (dist > radius + 1.0) discard; if (dist > radius - 1.0) { float dist = length(pixCoord); float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5); )#" + 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 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; } )#";07070100000040000081A4000000000000000000000001669CF85C00000072000000000000000000000000000000000000002700000000hyprlock-0.4.1/src/renderer/Shared.hpp#pragma once #include "Texture.hpp" struct SPreloadedAsset { CTexture texture; bool ready = false; };07070100000041000081A4000000000000000000000001669CF85C0000017F000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/renderer/Texture.cpp#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; }07070100000042000081A4000000000000000000000001669CF85C00000225000000000000000000000000000000000000002800000000hyprlock-0.4.1/src/renderer/Texture.hpp#pragma once #include <GLES3/gl32.h> #include "../helpers/Vector2D.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; };07070100000043000081A4000000000000000000000001669CF85C000016E8000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/renderer/mtx.hpp #pragma once #include <cstring> #include <wayland-client.h> #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; } 07070100000044000041ED000000000000000000000002669CF85C00000000000000000000000000000000000000000000002400000000hyprlock-0.4.1/src/renderer/widgets07070100000045000081A4000000000000000000000001669CF85C00000EAE000000000000000000000000000000000000003300000000hyprlock-0.4.1/src/renderer/widgets/Background.cpp#include "Background.hpp" #include "../Renderer.hpp" #include "../mtx.hpp" CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props, bool ss) : viewport(viewport_), resourceID(resourceID_), output(output_), isScreenshot(ss) { color = std::any_cast<Hyprlang::INT>(props.at("color")); blurPasses = std::any_cast<Hyprlang::INT>(props.at("blur_passes")); blurSize = std::any_cast<Hyprlang::INT>(props.at("blur_size")); vibrancy = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy")); vibrancy_darkness = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy_darkness")); noise = std::any_cast<Hyprlang::FLOAT>(props.at("noise")); brightness = std::any_cast<Hyprlang::FLOAT>(props.at("brightness")); contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast")); } void CBackground::renderRect(CColor color) { CBox monbox = {0, 0, viewport.x, viewport.y}; g_pRenderer->renderRect(monbox, color, 0); } bool CBackground::draw(const SRenderData& data) { if (resourceID.empty()) { CColor col = color; col.a *= data.opacity; renderRect(col); return data.opacity < 1.0; } if (!asset) asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID); if (!asset) { CColor col = color; col.a *= data.opacity; renderRect(col); return true; } if (asset->texture.m_iType == TEXTURE_INVALID) { g_pRenderer->asyncResourceGatherer->unloadAsset(asset); resourceID = ""; return true; } if ((blurPasses > 0 || isScreenshot) && !blurredFB.isAllocated()) { // make it brah Vector2D size = asset->texture.m_vSize; if (output->transform % 2 == 1 && isScreenshot) { size.x = asset->texture.m_vSize.y; size.y = asset->texture.m_vSize.x; } 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(); blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit blurredFB.bind(); g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, isScreenshot ? wlr_output_transform_invert(output->transform) : WL_OUTPUT_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs if (blurPasses > 0) g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } CTexture* tex = blurredFB.isAllocated() ? &blurredFB.m_cTex : &asset->texture; CBox texbox = {{}, tex->m_vSize}; Vector2D size = tex->m_vSize; float scaleX = viewport.x / tex->m_vSize.x; float scaleY = viewport.y / tex->m_vSize.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(); g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; }07070100000046000081A4000000000000000000000001669CF85C00000484000000000000000000000000000000000000003300000000hyprlock-0.4.1/src/renderer/widgets/Background.hpp#pragma once #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" #include "../Framebuffer.hpp" #include <string> #include <unordered_map> #include <any> struct SPreloadedAsset; class COutput; class CBackground : public IWidget { public: CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_); virtual bool draw(const SRenderData& data); void renderRect(CColor color); private: // if needed CFramebuffer blurredFB; 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 resourceID; CColor color; SPreloadedAsset* asset = nullptr; COutput* output = nullptr; bool isScreenshot = false; };07070100000047000081A4000000000000000000000001669CF85C00001992000000000000000000000000000000000000003000000000hyprlock-0.4.1/src/renderer/widgets/IWidget.cpp#include "IWidget.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "src/core/Auth.hpp" #include <chrono> #include <unistd.h> #include <pwd.h> #include <hyprutils/string/VarList.hpp> using namespace Hyprutils::String; #if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 190100 #pragma comment(lib, "date-tz") #include <date/tz.h> namespace std { namespace chrono { using date::current_zone; } } #endif 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 - size.rotated(ang)) / 2.0; Vector2D pos = offset; if (halign == "center") pos.x += viewport.x / 2.0 - size.x / 2.0; else if (halign == "left") pos.x += 0 - rot.x; else if (halign == "right") pos.x += viewport.x - size.x + rot.x; else if (halign != "none") Debug::log(ERR, "IWidget: invalid halign {}", halign); if (valign == "center") pos.y += viewport.y / 2.0 - size.y / 2.0; else if (valign == "top") pos.y += viewport.y - size.y + rot.y; else if (valign == "bottom") pos.y += 0 - rot.y; else if (valign != "none") Debug::log(ERR, "IWidget: invalid valign {}", valign); return pos; } static void replaceAll(std::string& str, const std::string& from, const std::string& to) { if (from.empty()) return; size_t pos = 0; while ((pos = str.find(from, pos)) != std::string::npos) { str.replace(pos, from.length(), to); pos += to.length(); } } static void replaceAllAttempts(std::string& str) { const size_t ATTEMPTS = g_pHyprlock->getPasswordFailedAttempts(); 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) { const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout; const auto LAYOUTNAME = xkb_keymap_layout_get_name(g_pHyprlock->m_pXKBKeymap, LAYOUTIDX); const std::string STR = LAYOUTNAME ? LAYOUTNAME : "error"; 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); const std::string LANG = LANGS[LAYOUTIDX].empty() ? STR : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX]; str.replace(pos, 9 + REPL.length(), LANG); pos += LANG.length(); } else { str.replace(pos, 7, STR); pos += STR.length(); } } } static std::string getTime() { const auto current_zone = std::chrono::current_zone(); const auto HHMMSS = std::chrono::hh_mm_ss{current_zone->to_local(std::chrono::system_clock::now()) - std::chrono::floor<std::chrono::days>(current_zone->to_local(std::chrono::system_clock::now()))}; 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); } IWidget::SFormatResult IWidget::formatString(std::string in) { auto uidPassword = getpwuid(getuid()); char* username = uidPassword->pw_name; char* user_gecos = uidPassword->pw_gecos; if (!username) Debug::log(ERR, "Error in formatString, username null. Errno: ", errno); if (!user_gecos) Debug::log(WARN, "Error in formatString, user_gecos null. Errno: ", errno); IWidget::SFormatResult result; replaceAll(in, "$DESC", std::string{user_gecos ? user_gecos : ""}); replaceAll(in, "$USER", std::string{username ? username : ""}); replaceAll(in, "<br/>", std::string{"\n"}); if (in.contains("$TIME")) { replaceAll(in, "$TIME", getTime()); result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } if (in.contains("$FAIL")) { const auto FAIL = g_pAuth->getLastFailText(); replaceAll(in, "$FAIL", FAIL.has_value() ? FAIL.value() : ""); result.allowForceUpdate = true; } if (in.contains("$PROMPT")) { const auto PROMPT = g_pAuth->getLastPrompt(); replaceAll(in, "$PROMPT", PROMPT.has_value() ? PROMPT.value() : ""); result.allowForceUpdate = true; } if (in.contains("$ATTEMPTS")) { replaceAllAttempts(in); result.allowForceUpdate = true; } if (in.contains("$LAYOUT")) { replaceAllLayout(in); result.allowForceUpdate = true; } if (in.starts_with("cmd[") && in.contains("]")) { // this is a command CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true); for (const auto& v : vars) { if (v.starts_with("update:")) { try { if (v.substr(7).contains(':')) { auto str = v.substr(v.substr(7).find_first_of(':') + 8); result.allowForceUpdate = str == "true" || std::stoull(str) == 1; } result.updateEveryMs = std::stoull(v.substr(7)); } catch (std::exception& e) { Debug::log(ERR, "Error parsing {} in cmd[]", v); } } else { Debug::log(ERR, "Unknown prop in string format {}", v); } } result.alwaysUpdate = true; in = in.substr(in.find_first_of(']') + 1); result.cmd = true; } result.formatted = in; return result; } 07070100000048000081A4000000000000000000000001669CF85C00000322000000000000000000000000000000000000003000000000hyprlock-0.4.1/src/renderer/widgets/IWidget.hpp#pragma once #include "../../helpers/Vector2D.hpp" #include <string> class IWidget { public: struct SRenderData { float opacity = 1; }; virtual ~IWidget() = default; virtual bool draw(const SRenderData& data) = 0; virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang = 0); struct SFormatResult { std::string formatted; float updateEveryMs = 0; // 0 means don't (static) bool alwaysUpdate = false; bool cmd = false; bool allowForceUpdate = false; }; virtual SFormatResult formatString(std::string in); }; 07070100000049000081A4000000000000000000000001669CF85C00001A0E000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Image.cpp#include "Image.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Log.hpp" #include <cmath> CImage::~CImage() { if (imageTimer) { imageTimer->cancel(); imageTimer.reset(); } } static void onTimer(std::shared_ptr<CTimer> self, void* data) { const auto PIMAGE = (CImage*)data; PIMAGE->onTimerUpdate(); PIMAGE->plantTimer(); } static void onAssetCallback(void* data) { const auto PIMAGE = (CImage*)data; PIMAGE->renderSuper(); } void CImage::onTimerUpdate() { const std::string OLDPATH = path; if (!reloadCommand.empty()) { path = g_pHyprlock->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(path); if (OLDPATH == path && MTIME == modificationTime) return; modificationTime = MTIME; } catch (std::exception& e) { path = OLDPATH; Debug::log(ERR, "{}", e.what()); return; } if (!pendingResourceID.empty()) return; request.id = std::string{"image:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count()); pendingResourceID = request.id; request.asset = path; request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE; request.callback = onAssetCallback; request.callbackData = this; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } void CImage::plantTimer() { if (reloadTime == 0) { imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); } else if (reloadTime > 0) imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false); } CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) : viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) { size = std::any_cast<Hyprlang::INT>(props.at("size")); rounding = std::any_cast<Hyprlang::INT>(props.at("rounding")); border = std::any_cast<Hyprlang::INT>(props.at("border_size")); color = std::any_cast<Hyprlang::INT>(props.at("border_color")); pos = std::any_cast<Hyprlang::VEC2>(props.at("position")); halign = std::any_cast<Hyprlang::STRING>(props.at("halign")); valign = std::any_cast<Hyprlang::STRING>(props.at("valign")); angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate")); path = std::any_cast<Hyprlang::STRING>(props.at("path")); reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time")); reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd")); try { modificationTime = std::filesystem::last_write_time(path); } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } angle = angle * M_PI / 180.0; plantTimer(); } bool CImage::draw(const SRenderData& data) { if (!pendingResourceID.empty()) { auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { if (newAsset->texture.m_iType == TEXTURE_INVALID) { g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); } else if (resourceID != pendingResourceID) { g_pRenderer->asyncResourceGatherer->unloadAsset(asset); imageFB.release(); asset = newAsset; resourceID = pendingResourceID; firstRender = true; } pendingResourceID = ""; } } if (resourceID.empty()) return false; if (!asset) asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID); if (!asset) return true; if (asset->texture.m_iType == TEXTURE_INVALID) { g_pRenderer->asyncResourceGatherer->unloadAsset(asset); resourceID = ""; return false; } if (!imageFB.isAllocated()) { const Vector2D IMAGEPOS = {border, border}; const Vector2D BORDERPOS = {0.0, 0.0}; const Vector2D TEXSIZE = asset->texture.m_vSize; const float SCALEX = size / TEXSIZE.x; const float SCALEY = size / TEXSIZE.y; // image with borders offset, with extra pixel for anti-aliasing when rotated CBox texbox = {angle == 0 ? IMAGEPOS : IMAGEPOS + Vector2D{1.0, 1.0}, TEXSIZE}; texbox.w *= std::max(SCALEX, SCALEY); texbox.h *= std::max(SCALEX, SCALEY); const bool ALLOWROUND = rounding > -1 && rounding < std::min(texbox.w, texbox.h) / 2.0; // 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}; 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->renderRect(borderBox, color, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : std::min(borderBox.w, borderBox.h) / 2.0); texbox.round(); g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, WL_OUTPUT_TRANSFORM_NORMAL); g_pRenderer->popFb(); } CTexture* tex = &imageFB.m_cTex; CBox texbox = {{}, tex->m_vSize}; if (firstRender) { firstRender = false; shadow.markShadowDirty(); } shadow.draw(data); const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle); texbox.x = TEXPOS.x; texbox.y = TEXPOS.y; texbox.round(); texbox.rot = angle; g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; } static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) { const auto PIMAGE = (CImage*)data; PIMAGE->renderSuper(); } void CImage::renderSuper() { g_pHyprlock->renderOutput(output->stringPort); if (!pendingResourceID.empty()) /* did not consume the pending resource */ g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this); } 0707010000004A000081A4000000000000000000000001669CF85C00000733000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Image.hpp#pragma once #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" #include "Shadowable.hpp" #include <string> #include <filesystem> #include <unordered_map> #include <any> struct SPreloadedAsset; class COutput; class CImage : public IWidget { public: CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props); ~CImage(); virtual bool draw(const SRenderData& data); void renderSuper(); void onTimerUpdate(); void plantTimer(); private: CFramebuffer imageFB; int size; int rounding; double border; double angle; CColor color; Vector2D pos; std::string halign, valign, path; bool firstRender = true; int reloadTime; std::string reloadCommand; std::filesystem::file_time_type modificationTime; std::shared_ptr<CTimer> imageTimer; CAsyncResourceGatherer::SPreloadRequest request; Vector2D viewport; std::string resourceID; std::string pendingResourceID; // if reloading image SPreloadedAsset* asset = nullptr; COutput* output = nullptr; CShadowable shadow; }; 0707010000004B000081A4000000000000000000000001669CF85C000013AE000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Label.cpp#include "Label.hpp" #include "../../helpers/Color.hpp" #include <hyprlang.hpp> #include <stdexcept> #include "../Renderer.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" CLabel::~CLabel() { if (labelTimer) { labelTimer->cancel(); labelTimer.reset(); } } static void onTimer(std::shared_ptr<CTimer> self, void* data) { if (data == nullptr) return; const auto PLABEL = (CLabel*)data; // update label PLABEL->onTimerUpdate(); // plant new timer PLABEL->plantTimer(); } static void onAssetCallback(void* data) { const auto PLABEL = (CLabel*)data; PLABEL->renderSuper(); } std::string CLabel::getUniqueResourceId() { return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); } void CLabel::onTimerUpdate() { std::string oldFormatted = label.formatted; label = formatString(labelPreFormat); if (label.formatted == oldFormatted && !label.alwaysUpdate) return; if (!pendingResourceID.empty()) { Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID); return; } // request new request.id = getUniqueResourceId(); pendingResourceID = request.id; request.asset = label.formatted; request.callback = onAssetCallback; request.callbackData = this; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } void CLabel::plantTimer() { if (label.updateEveryMs != 0) labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), onTimer, this, label.allowForceUpdate); else if (label.updateEveryMs == 0 && label.allowForceUpdate) labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); } CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) : outputStringPort(output), shadow(this, props, viewport_) { try { labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text")); std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align")); std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family")); CColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color")); int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size")); label = formatString(labelPreFormat); request.id = getUniqueResourceId(); resourceID = request.id; request.asset = label.formatted; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = fontFamily; request.props["color"] = labelColor; request.props["font_size"] = fontSize; request.props["cmd"] = label.cmd; if (!textAlign.empty()) request.props["text_align"] = textAlign; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position")); pos = {POS__.x, POS__.y}; configPos = pos; viewport = viewport_; halign = std::any_cast<Hyprlang::STRING>(props.at("halign")); valign = std::any_cast<Hyprlang::STRING>(props.at("valign")); angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate")); angle = angle * M_PI / 180.0; plantTimer(); } catch (const std::bad_any_cast& e) { Debug::log(ERR, "Failed to construct CLabel: {}", e.what()); throw; } catch (const std::out_of_range& e) { Debug::log(ERR, "Missing propperty for CLabel:{}", e.what()); throw; } } bool CLabel::draw(const SRenderData& data) { if (!asset) { asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID); if (!asset) return true; shadow.markShadowDirty(); } if (!pendingResourceID.empty()) { // new asset is pending auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { // new asset is ready :D g_pRenderer->asyncResourceGatherer->unloadAsset(asset); asset = newAsset; resourceID = pendingResourceID; pendingResourceID = ""; shadow.markShadowDirty(); } } shadow.draw(data); // calc pos pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle); CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y}; box.rot = angle; g_pRenderer->renderTexture(box, asset->texture, data.opacity); return false; } void CLabel::renderSuper() { g_pHyprlock->renderOutput(outputStringPort); } 0707010000004C000081A4000000000000000000000001669CF85C000005E6000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Label.hpp#pragma once #include "IWidget.hpp" #include "Shadowable.hpp" #include "../../helpers/Vector2D.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" #include <string> #include <unordered_map> #include <any> struct SPreloadedAsset; class CSessionLockSurface; class CLabel : public IWidget { public: CLabel(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output); ~CLabel(); virtual bool draw(const SRenderData& data); void renderSuper(); void onTimerUpdate(); void plantTimer(); private: std::string getUniqueResourceId(); std::string labelPreFormat; IWidget::SFormatResult label; Vector2D viewport; Vector2D pos; Vector2D configPos; double angle; std::string resourceID; std::string pendingResourceID; // if dynamic label std::string halign, valign; SPreloadedAsset* asset = nullptr; std::string outputStringPort; CAsyncResourceGatherer::SPreloadRequest request; std::shared_ptr<CTimer> labelTimer = nullptr; CShadowable shadow; }; 0707010000004D000081A4000000000000000000000001669CF85C000056B6000000000000000000000000000000000000003B00000000hyprlock-0.4.1/src/renderer/widgets/PasswordInputField.cpp#include "PasswordInputField.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" #include "src/core/Auth.hpp" #include <algorithm> static void replaceAll(std::string& str, const std::string& from, const std::string& to) { if (from.empty()) return; size_t pos = 0; while ((pos = str.find(from, pos)) != std::string::npos) { str.replace(pos, from.length(), to); pos += to.length(); } } CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) : outputStringPort(output), shadow(this, props, viewport_) { size = std::any_cast<Hyprlang::VEC2>(props.at("size")); outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness")); dots.size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size")); dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing")); dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center")); dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding")); fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty")); fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout")); hiddenInputState.enabled = std::any_cast<Hyprlang::INT>(props.at("hide_input")); rounding = std::any_cast<Hyprlang::INT>(props.at("rounding")); configPlaceholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text")); configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text")); configFailTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fail_timeout")); col.transitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition")); col.outer = std::any_cast<Hyprlang::INT>(props.at("outer_color")); col.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color")); col.font = std::any_cast<Hyprlang::INT>(props.at("font_color")); col.fail = std::any_cast<Hyprlang::INT>(props.at("fail_color")); col.check = std::any_cast<Hyprlang::INT>(props.at("check_color")); col.both = std::any_cast<Hyprlang::INT>(props.at("bothlock_color")); col.caps = std::any_cast<Hyprlang::INT>(props.at("capslock_color")); col.num = std::any_cast<Hyprlang::INT>(props.at("numlock_color")); col.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock")); col.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color")); viewport = viewport_; auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position")); pos = {POS__.x, POS__.y}; configPos = pos; configSize = size; halign = std::any_cast<Hyprlang::STRING>(props.at("halign")); valign = std::any_cast<Hyprlang::STRING>(props.at("valign")); pos = posFromHVAlign(viewport, size, pos, halign, valign); dots.size = std::clamp(dots.size, 0.2f, 0.8f); dots.spacing = std::clamp(dots.spacing, 0.f, 1.f); col.transitionMs = std::clamp(col.transitionMs, 0, 1000); col.both = col.both == -1 ? col.outer : col.both; col.caps = col.caps == -1 ? col.outer : col.caps; col.num = col.num == -1 ? col.outer : col.num; g_pHyprlock->m_bNumLock = col.invertNum; // Render placeholder if either placeholder_text or fail_text are non-empty // as placeholder must be rendered to show fail_text if (!configPlaceholderText.empty() || !configFailText.empty()) { placeholder.currentText = configPlaceholderText; replaceAll(placeholder.currentText, "$PROMPT", ""); placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this); CAsyncResourceGatherer::SPreloadRequest request; request.id = placeholder.resourceID; request.asset = placeholder.currentText; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = std::string{"Sans"}; request.props["color"] = CColor{1.0 - col.font.r, 1.0 - col.font.g, 1.0 - col.font.b, 0.5}; request.props["font_size"] = (int)size.y / 4; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } } static void fadeOutCallback(std::shared_ptr<CTimer> self, void* data) { CPasswordInputField* p = (CPasswordInputField*)data; p->onFadeOutTimer(); } void CPasswordInputField::onFadeOutTimer() { fade.allowFadeOut = true; fade.fadeOutTimer.reset(); g_pHyprlock->renderOutput(outputStringPort); } void CPasswordInputField::updateFade() { if (!fadeOnEmpty) { fade.a = 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 != 0.0 && (!fade.animated || fade.appearing)) { if (fade.allowFadeOut || fadeTimeoutMs == 0) { fade.a = 1.0; fade.animated = true; fade.appearing = false; fade.start = std::chrono::system_clock::now(); fade.allowFadeOut = false; } else if (!fade.fadeOutTimer.get()) fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), fadeOutCallback, this); } if (INPUTUSED && fade.a != 1.0 && (!fade.animated || !fade.appearing)) { fade.a = 0.0; fade.animated = true; fade.appearing = true; fade.start = std::chrono::system_clock::now(); } if (fade.animated) { if (fade.appearing) fade.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0); else fade.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0); if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0)) fade.animated = false; redrawShadow = true; } } void CPasswordInputField::updateDots() { if (passwordLength == dots.currentAmount) return; if (std::abs(passwordLength - dots.currentAmount) > 1) { dots.currentAmount = std::clamp(dots.currentAmount, passwordLength - 1.f, passwordLength + 1.f); dots.lastFrame = std::chrono::system_clock::now(); } const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000); const float TOADD = DELTA / 1000000.0 * dots.speedPerSecond; if (passwordLength > dots.currentAmount) { dots.currentAmount += TOADD; if (dots.currentAmount > passwordLength) dots.currentAmount = passwordLength; } else if (passwordLength < dots.currentAmount) { dots.currentAmount -= TOADD; if (dots.currentAmount < passwordLength) dots.currentAmount = passwordLength; } dots.lastFrame = std::chrono::system_clock::now(); } bool CPasswordInputField::draw(const SRenderData& data) { CBox inputFieldBox = {pos, size}; CBox outerBox = {pos - Vector2D{outThick, outThick}, size + Vector2D{outThick * 2, outThick * 2}}; if (firstRender || redrawShadow) { firstRender = false; redrawShadow = false; shadow.markShadowDirty(); } bool forceReload = false; passwordLength = g_pHyprlock->getPasswordBufferDisplayLen(); checkWaiting = g_pAuth->checkWaiting(); updateFade(); updateDots(); updatePlaceholder(); updateColors(); updateHiddenInputState(); static auto TIMER = std::chrono::system_clock::now(); if (placeholder.asset) { const auto TARGETSIZEX = placeholder.asset->texture.m_vSize.x + inputFieldBox.h; if (size.x < TARGETSIZEX) { const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - TIMER).count(), 8000, 20000); TIMER = std::chrono::system_clock::now(); forceReload = true; size.x += std::clamp((TARGETSIZEX - size.x) * DELTA / 100000.0, 1.0, 1000.0); if (size.x > TARGETSIZEX) { size.x = TARGETSIZEX; redrawShadow = true; } } pos = posFromHVAlign(viewport, size, configPos, halign, valign); } else if (size.x != configSize.x) { size.x = configSize.x; pos = posFromHVAlign(viewport, size, configPos, halign, valign); } SRenderData shadowData = data; shadowData.opacity *= fade.a; shadow.draw(shadowData); CColor outerCol = col.outer; outerCol.a *= fade.a * data.opacity; CColor innerCol = col.inner; innerCol.a *= fade.a * data.opacity; CColor fontCol = col.font; fontCol.a *= fade.a * data.opacity; if (outThick > 0) { g_pRenderer->renderRect(outerBox, outerCol, rounding == -1 ? outerBox.h / 2.0 : rounding); if (passwordLength != 0 && hiddenInputState.enabled && !fade.animated && data.opacity == 1.0) { 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->renderRect(outerBox, hiddenInputState.lastColor, rounding == -1 ? outerBox.h / 2.0 : rounding); glScissor(0, 0, viewport.x, viewport.y); glDisable(GL_SCISSOR_TEST); } } g_pRenderer->renderRect(inputFieldBox, innerCol, rounding == -1 ? inputFieldBox.h / 2.0 : rounding - outThick); if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) { const int PASS_SIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f; const int PASS_SPACING = std::floor(PASS_SIZE * dots.spacing); const int DOT_PAD = (inputFieldBox.h - PASS_SIZE) / 2; const int DOT_AREA_WIDTH = inputFieldBox.w - DOT_PAD * 2; // avail width for dots const int MAX_DOTS = std::round(DOT_AREA_WIDTH * 1.0 / (PASS_SIZE + PASS_SPACING)); // max amount of dots that can fit in the area const int DOT_FLOORED = std::floor(dots.currentAmount); const float DOT_ALPHA = fontCol.a; // Calculate the total width required for all dots including spaces between them const int TOTAL_DOTS_WIDTH = (PASS_SIZE + PASS_SPACING) * dots.currentAmount - PASS_SPACING; // Calculate starting x-position to ensure dots stay centered within the input field int xstart = dots.center ? (DOT_AREA_WIDTH - TOTAL_DOTS_WIDTH) / 2 + DOT_PAD : DOT_PAD; if (dots.currentAmount > MAX_DOTS) xstart = (inputFieldBox.w + MAX_DOTS * (PASS_SIZE + PASS_SPACING) - PASS_SPACING - 2 * TOTAL_DOTS_WIDTH) / 2; if (dots.rounding == -1) dots.rounding = PASS_SIZE / 2.0; else if (dots.rounding == -2) dots.rounding = rounding == -1 ? PASS_SIZE / 2.0 : rounding * dots.size; for (int i = 0; i < dots.currentAmount; ++i) { if (i < DOT_FLOORED - MAX_DOTS) continue; if (dots.currentAmount != DOT_FLOORED) { if (i == DOT_FLOORED) fontCol.a *= (dots.currentAmount - DOT_FLOORED) * data.opacity; else if (i == DOT_FLOORED - MAX_DOTS) fontCol.a *= (1 - dots.currentAmount + DOT_FLOORED) * data.opacity; } Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart + (int)inputFieldBox.w % 2 / 2.f + i * (PASS_SIZE + PASS_SPACING), inputFieldBox.h / 2.f - PASS_SIZE / 2.f}; CBox box{dotPosition, Vector2D{PASS_SIZE, PASS_SIZE}}; g_pRenderer->renderRect(box, fontCol, dots.rounding); fontCol.a = DOT_ALPHA; } } if (passwordLength == 0 && !placeholder.resourceID.empty()) { SPreloadedAsset* currAsset = nullptr; if (!placeholder.asset) placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); currAsset = placeholder.asset; if (currAsset) { Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; pos = pos - currAsset->texture.m_vSize / 2.f; CBox textbox{pos, currAsset->texture.m_vSize}; g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0); } else forceReload = true; } return dots.currentAmount != passwordLength || fade.animated || col.animated || redrawShadow || data.opacity < 1.0 || forceReload; } static void failTimeoutCallback(std::shared_ptr<CTimer> self, void* data) { g_pAuth->m_bDisplayFailText = false; g_pHyprlock->renderAllOutputs(); } void CPasswordInputField::updatePlaceholder() { if (passwordLength != 0) { if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ placeholder.isFailText) { std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID = ""; redrawShadow = true; } return; } const auto AUTHFEEDBACK = g_pAuth->m_bDisplayFailText ? g_pAuth->getLastFailText().value_or("Ups, no fail text?") : g_pAuth->getLastPrompt().value_or("Ups, no prompt?"); if (placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts) return; placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); placeholder.isFailText = g_pAuth->m_bDisplayFailText; placeholder.lastAuthFeedback = AUTHFEEDBACK; placeholder.asset = nullptr; if (placeholder.isFailText) { g_pHyprlock->addTimer(std::chrono::milliseconds(configFailTimeoutMs), failTimeoutCallback, nullptr); placeholder.currentText = configFailText; replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK); replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(placeholder.failedAttempts)); } else { placeholder.currentText = configPlaceholderText; replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK); } placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this); if (std::find(placeholder.registeredResourceIDs.begin(), placeholder.registeredResourceIDs.end(), placeholder.resourceID) != placeholder.registeredResourceIDs.end()) return; placeholder.registeredResourceIDs.push_back(placeholder.resourceID); // query CAsyncResourceGatherer::SPreloadRequest request; request.id = placeholder.resourceID; request.asset = placeholder.currentText; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = std::string{"Sans"}; request.props["color"] = (placeholder.isFailText) ? col.fail : col.font; request.props["font_size"] = (int)size.y / 4; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } void CPasswordInputField::updateHiddenInputState() { if (!hiddenInputState.enabled || (size_t)hiddenInputState.lastPasswordLength == passwordLength) return; // randomize new thang hiddenInputState.lastPasswordLength = passwordLength; srand(std::chrono::system_clock::now().time_since_epoch().count()); float r1 = (rand() % 100) / 255.0; float r2 = (rand() % 100) / 255.0; int r3 = rand() % 3; int r4 = rand() % 2; int r5 = rand() % 2; ((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0; ((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0; for (int i = 0; i < 3; ++i) { if (i != r3 && i != ((r3 + r4) % 3)) { ((float*)&hiddenInputState.lastColor.r)[i] = 1.0 - ((float*)&hiddenInputState.lastColor.r)[r5 ? r3 : ((r3 + r4) % 3)]; } } hiddenInputState.lastColor.a = 1.0; hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4; } static void changeChannel(const float& source, const float& target, float& subject, const double& multi, bool& animated) { const float DELTA = target - source; if (subject != target) { subject += DELTA * multi; animated = true; if ((source < target && subject > target) || (source > target && subject < target)) subject = target; } } static void changeColor(const CColor& source, const CColor& target, CColor& subject, const double& multi, bool& animated) { changeChannel(source.r, target.r, subject.r, multi, animated); changeChannel(source.g, target.g, subject.g, multi, animated); changeChannel(source.b, target.b, subject.b, multi, animated); changeChannel(source.a, target.a, subject.a, multi, animated); } void CPasswordInputField::updateColors() { static auto OUTER = col.outer, TARGET = OUTER, SOURCE = OUTER; static auto INNER = col.inner, ITARGET = INNER, ISOURCE = INNER; static auto FONT = col.font, FTARGET = FONT, FSOURCE = FONT; const bool BORDERLESS = outThick == 0; if (col.animated) { // some cases when events happen too quick (within transitionMs) // TODO: find more? const bool LOCKCHANGED = col.stateNum != (col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock) || col.stateCaps != g_pHyprlock->m_bCapsLock; const bool ANIMONCHECK = checkWaiting && (TARGET == (BORDERLESS ? INNER : OUTER) || TARGET == col.fail); if (LOCKCHANGED || ANIMONCHECK) { const bool EQUALCOLORS = ANIMONCHECK && OUTER == col.check; // to avoid throttle when check_color set to the same as outer. SOURCE = BORDERLESS ? (EQUALCOLORS ? INNER : col.inner) : col.outer; FSOURCE = EQUALCOLORS ? FONT : col.font; ISOURCE = EQUALCOLORS ? INNER : col.inner; } } else { SOURCE = BORDERLESS ? col.inner : col.outer; FSOURCE = col.font; ISOURCE = col.inner; } col.stateNum = col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock; col.stateCaps = g_pHyprlock->m_bCapsLock; if (!placeholder.isFailText || passwordLength > 0 || (passwordLength == 0 && checkWaiting)) { if (g_pHyprlock->m_bFadeStarted) { if (TARGET == col.check) SOURCE = BORDERLESS ? col.inner : col.outer; col.transitionMs = 100; TARGET = BORDERLESS ? INNER : OUTER; } else if (checkWaiting) { FTARGET = col.swapFont ? INNER : FONT; const float PASSALPHA = FTARGET.a * 0.5; FTARGET.a = PASSALPHA; TARGET = col.check; ITARGET = col.swapFont ? FONT : INNER; } else if (col.stateCaps && col.stateNum && col.both != OUTER) { TARGET = col.both; FTARGET = col.swapFont && BORDERLESS ? INNER : FONT; } else if (col.stateCaps && col.caps != OUTER) { TARGET = col.caps; FTARGET = col.swapFont && BORDERLESS ? INNER : FONT; } else if (col.stateNum && col.num != OUTER) { TARGET = col.num; FTARGET = col.swapFont && BORDERLESS ? INNER : FONT; } else { // if quickly pressed after failure if (col.animated && TARGET == col.fail) SOURCE = BORDERLESS ? col.inner : col.outer; TARGET = BORDERLESS ? INNER : OUTER; FTARGET = FONT; ITARGET = INNER; } } else { FSOURCE = col.swapFont ? INNER : FONT; const float PASSALPHA = FSOURCE.a * 0.5; FSOURCE.a = PASSALPHA; FTARGET = FONT; SOURCE = col.check; TARGET = col.fail; ISOURCE = FONT; ITARGET = FONT; if (fade.animated || fade.a < 1.0) { TARGET = BORDERLESS ? INNER : OUTER; SOURCE = col.fail; } } col.animated = false; const bool SWAPDONE = !BORDERLESS && col.swapFont ? col.inner == ITARGET : true; if ((BORDERLESS ? col.inner : col.outer) == TARGET && col.font == FTARGET && SWAPDONE) { col.shouldStart = true; return; } if (col.shouldStart) { col.lastFrame = std::chrono::system_clock::now(); col.shouldStart = false; } const auto MULTI = col.transitionMs == 0 ? 1.0 : std::clamp(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - col.lastFrame).count() / (double)col.transitionMs, 0.016, 0.5); changeColor(SOURCE, TARGET, (BORDERLESS ? col.inner : col.outer), MULTI, col.animated); changeColor(FSOURCE, FTARGET, col.font, MULTI, col.animated); if (col.swapFont && !BORDERLESS) changeColor(ISOURCE, ITARGET, col.inner, MULTI, col.animated); col.lastFrame = std::chrono::system_clock::now(); } 0707010000004E000081A4000000000000000000000001669CF85C00000D13000000000000000000000000000000000000003B00000000hyprlock-0.4.1/src/renderer/widgets/PasswordInputField.hpp#pragma once #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" #include "../../core/Timer.hpp" #include "Shadowable.hpp" #include <chrono> #include <vector> #include <any> #include <unordered_map> struct SPreloadedAsset; class CPasswordInputField : public IWidget { public: CPasswordInputField(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output); virtual bool draw(const SRenderData& data); void onFadeOutTimer(); private: void updateDots(); void updateFade(); void updatePlaceholder(); void updateHiddenInputState(); void updateColors(); bool firstRender = true; bool redrawShadow = false; bool checkWaiting = false; size_t passwordLength = 0; Vector2D size; Vector2D pos; Vector2D viewport; Vector2D configPos; Vector2D configSize; std::string halign, valign, configFailText, outputStringPort, configPlaceholderText; uint64_t configFailTimeoutMs = 2000; int outThick, rounding; struct { float currentAmount = 0; float speedPerSecond = 5; // actually per... something. I am unsure xD std::chrono::system_clock::time_point lastFrame; bool center = false; float size = 0; float spacing = 0; int rounding = 0; } dots; struct { std::chrono::system_clock::time_point start; float a = 0; bool appearing = true; bool animated = false; std::shared_ptr<CTimer> fadeOutTimer = nullptr; bool allowFadeOut = false; } fade; struct { std::string resourceID = ""; SPreloadedAsset* asset = nullptr; std::string currentText = ""; size_t failedAttempts = 0; bool canGetNewText = true; bool isFailText = false; std::string lastAuthFeedback; std::vector<std::string> registeredResourceIDs; } placeholder; struct { CColor lastColor; int lastQuadrant = 0; int lastPasswordLength = 0; bool enabled = false; } hiddenInputState; struct { CColor outer; CColor inner; CColor font; CColor fail; CColor check; CColor caps; CColor num; CColor both; int transitionMs = 0; bool invertNum = false; bool animated = false; bool stateNum = false; bool stateCaps = false; bool swapFont = false; bool shouldStart; // std::chrono::system_clock::time_point lastFrame; } col; bool fadeOnEmpty; uint64_t fadeTimeoutMs; CShadowable shadow; }; 0707010000004F000081A4000000000000000000000001669CF85C00000562000000000000000000000000000000000000003300000000hyprlock-0.4.1/src/renderer/widgets/Shadowable.cpp#include "Shadowable.hpp" #include "../Renderer.hpp" CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) : widget(widget_), viewport(viewport_) { size = std::any_cast<Hyprlang::INT>(props.at("shadow_size")); passes = std::any_cast<Hyprlang::INT>(props.at("shadow_passes")); color = std::any_cast<Hyprlang::INT>(props.at("shadow_color")); boostA = std::any_cast<Hyprlang::FLOAT>(props.at("shadow_boost")); } void CShadowable::markShadowDirty() { 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 (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, WL_OUTPUT_TRANSFORM_NORMAL); return true; }07070100000050000081A4000000000000000000000001669CF85C00000322000000000000000000000000000000000000003300000000hyprlock-0.4.1/src/renderer/widgets/Shadowable.hpp#pragma once #include "../Framebuffer.hpp" #include "../../helpers/Color.hpp" #include "IWidget.hpp" #include <string> #include <unordered_map> #include <any> class CShadowable { public: CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& 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: IWidget* widget = nullptr; int size = 10; int passes = 4; float boostA = 1.0; CColor color{0, 0, 0, 1.0}; Vector2D viewport; // to avoid recursive shadows bool ignoreDraw = false; CFramebuffer shadowFB; };07070100000051000081A4000000000000000000000001669CF85C00000CE1000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Shape.cpp#include "Shape.hpp" #include "../Renderer.hpp" #include <cmath> CShape::CShape(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) : shadow(this, props, viewport_) { size = std::any_cast<Hyprlang::VEC2>(props.at("size")); rounding = std::any_cast<Hyprlang::INT>(props.at("rounding")); border = std::any_cast<Hyprlang::INT>(props.at("border_size")); color = std::any_cast<Hyprlang::INT>(props.at("color")); borderColor = std::any_cast<Hyprlang::INT>(props.at("border_color")); pos = std::any_cast<Hyprlang::VEC2>(props.at("position")); halign = std::any_cast<Hyprlang::STRING>(props.at("halign")); valign = std::any_cast<Hyprlang::STRING>(props.at("valign")); angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate")); xray = std::any_cast<Hyprlang::INT>(props.at("xray")); viewport = viewport_; 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)); CColor borderCol = borderColor; borderCol.a *= data.opacity; g_pRenderer->renderRect(borderBox, borderCol, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND)); } 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 auto MINHALFSHAPE = std::min(shapeBox.w, shapeBox.h) / 2.0; const bool ALLOWROUND = rounding > -1 && rounding < MINHALFSHAPE; 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->renderRect(borderBox, borderColor, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER); g_pRenderer->renderRect(shapeBox, color, ALLOWROUND ? rounding : MINHALFSHAPE); 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, WL_OUTPUT_TRANSFORM_FLIPPED_180); return data.opacity < 1.0; } 07070100000052000081A4000000000000000000000001669CF85C00000341000000000000000000000000000000000000002E00000000hyprlock-0.4.1/src/renderer/widgets/Shape.hpp#pragma once #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Box.hpp" #include "Shadowable.hpp" #include <string> #include <unordered_map> #include <any> class CShape : public IWidget { public: CShape(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props); virtual bool draw(const SRenderData& data); private: CFramebuffer shapeFB; int rounding; double border; double angle; CColor color; CColor borderColor; Vector2D size; Vector2D pos; CBox shapeBox; CBox borderBox; bool xray; std::string halign, valign; bool firstRender = true; Vector2D viewport; CShadowable shadow; }; 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!561 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor