File hyprlock-0.9.1.obscpio of Package hyprlock
07070100000000000081A4000000000000000000000001688B5FE90000070E000000000000000000000000000000000000001D00000000hyprlock-0.9.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
07070100000001000081A4000000000000000000000001688B5FE900000EBA000000000000000000000000000000000000001B00000000hyprlock-0.9.1/.clang-tidyWarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase
07070100000002000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001700000000hyprlock-0.9.1/.github07070100000003000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000002600000000hyprlock-0.9.1/.github/ISSUE_TEMPLATE07070100000004000081A4000000000000000000000001688B5FE900000B60000000000000000000000000000000000000002E00000000hyprlock-0.9.1/.github/ISSUE_TEMPLATE/bug.ymlname: Bug Report
description: Something is not working right
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
## Before opening a new issue, please take a moment to search through the current open and closed issues to check if it already exists.
---
- type: dropdown
id: type
attributes:
label: Regression?
description: |
Regression means that something used to work but no longer does.
**BEFORE CONTINUING**, please check if this bug is a regression or not, and if it is, we need you to bisect with the help of the wiki: https://wiki.hyprland.org/Crashes-and-Bugs/#bisecting-an-issue
multiple: true
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: ver
attributes:
label: Hyprlock Info and Version
description: |
Please enter your hyprlock version and config.
Use `hyprlock --version` if it is available (Introduced after v0.4.1).
Otherwise please figure out the hyprlock version via your distribution.
If you built it yourself, post the commit hash.
Provide your hyprlock config. Usually `~/.config/hypr/hyprlock.conf`.
value: "<Enter your hyprlock version here>
<details>
<summary>Hyprlock config</summary>
```sh
<Paste your hyprlock config here>
```
</details>"
validations:
required: true
- type: textarea
id: compositor
attributes:
label: Compositor Info and Version
description: |
If you are on hyprland, paste the output of `hyprctl systeminfo` here.
If you are on another compositor, enter the compositor name and version you are on.
value: "<details>
<summary>System/Version info</summary>
```sh
<Paste the output of the command here>
```
</details>"
validations:
required: true
- type: textarea
id: desc
attributes:
label: Description
description: "What went wrong?"
validations:
required: true
- type: textarea
id: repro
attributes:
label: How to reproduce
description: "How can someone else reproduce the issue?"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Crash reports, logs, images, videos
description: |
Anything that can help. Please always ATTACH and not paste them.
To get hyprlock logs, start it from the commandline.
If you are using hypridle/swayidle as a systemd service, you can use `journalctl -b0 --user -u hypridle/swayidle` to get some logs.
Compositor logs and crashes are sometimes relevant as well.
Hyprland logs can be found in $XDG_RUNTIME_DIR/hypr.
Hyprland crash reports are stored in ~/.cache/hyprland or $XDG_CACHE_HOME/hyprland.
07070100000005000081A4000000000000000000000001688B5FE9000001A1000000000000000000000000000000000000003200000000hyprlock-0.9.1/.github/ISSUE_TEMPLATE/feature.ymlname: Feature Request
description: I'd like to request additional functionality
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before opening a new issue, take a moment to search through the current open ones.
---
- type: textarea
id: desc
attributes:
label: Description
description: "Describe your idea"
validations:
required: true
07070100000006000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000002100000000hyprlock-0.9.1/.github/workflows07070100000007000081A4000000000000000000000001688B5FE900000647000000000000000000000000000000000000002900000000hyprlock-0.9.1/.github/workflows/nix.ymlname: Build
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
# not needed (yet)
# - uses: cachix/cachix-action@v12
# with:
# name: hyprland
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: nix flake check --print-build-logs --keep-going
07070100000008000081A4000000000000000000000001688B5FE9000000AC000000000000000000000000000000000000001A00000000hyprlock-0.9.1/.gitignore**/.cache
.direnv
.envrc
.vscode/
CMakeCache.txt
CMakeFiles/
Makefile
cmake_install.cmake
build/
compile_commands.json
protocols/*.cpp
protocols/*.hpp
*.kdev4
.gdb_history
07070100000009000081A4000000000000000000000001688B5FE9000012E0000000000000000000000000000000000000001E00000000hyprlock-0.9.1/CMakeLists.txtcmake_minimum_required(VERSION 3.27)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VERSION)
project(
hyprlock
DESCRIPTION "A gpu-accelerated screen lock for Hyprland"
VERSION ${VERSION})
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprlock in Debug with CMake")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprlock in Release with CMake")
endif()
include_directories(. "protocols/")
include(GNUInstallDirs)
# configure
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
-Wno-missing-field-initializers -Wno-narrowing)
add_compile_definitions(HYPRLOCK_VERSION="${VERSION}")
if (DEFINED HYPRLOCK_COMMIT)
add_compile_definitions(HYPRLOCK_COMMIT="${HYPRLOCK_COMMIT}")
else()
# get git commit
execute_process(
OUTPUT_VARIABLE GIT_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND git rev-parse --short HEAD
)
add_compile_definitions(HYPRLOCK_COMMIT="${GIT_SHORT_HASH}")
endif()
if (DEFINED HYPRLOCK_VERSION_COMMIT)
add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${HYPRLOCK_VERSION_COMMIT}")
else()
# get git commit of v$VERSION
execute_process(
OUTPUT_VARIABLE GIT_TAG_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND git rev-parse --short v${VERSION}
)
add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${GIT_TAG_SHORT_HASH}")
endif()
message(STATUS "VERSION COMMIT: ${HYPRLOCK_VERSION_COMMIT}")
message(STATUS "COMMIT: ${HYPRLOCK_COMMIT}")
# position independent build: __FILE__
add_compile_options(-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
# dependencies
message(STATUS "Checking deps...")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
wayland-client
wayland-protocols>=1.35
wayland-egl
hyprlang>=0.6.0
egl
xkbcommon
cairo
pangocairo
libdrm
gbm
pam
hyprutils>=0.8.0
sdbus-c++>=2.0.0
hyprgraphics)
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::GLES3)
# protocols
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
message(
STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
function(protocolnew protoPath protoName external)
if(external)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
else()
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
endif()
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}.cpp
protocols/${protoName}.hpp)
endfunction()
function(protocolWayland)
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
COMMAND hyprwayland-scanner --wayland-enums --client
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
endfunction()
make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so
# the dir won't be there
protocolwayland()
protocolnew("protocols" "wlr-screencopy-unstable-v1" true)
protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false)
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
protocolnew("stable/viewporter" "viewporter" false)
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
protocolnew("stable/tablet" "tablet-v2" false)
# Installation
install(TARGETS hyprlock)
install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d)
install(
FILES ${CMAKE_SOURCE_DIR}/assets/example.conf
DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr
RENAME hyprlock.conf)
0707010000000A000081A4000000000000000000000001688B5FE9000005DF000000000000000000000000000000000000001700000000hyprlock-0.9.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.
0707010000000B000081A4000000000000000000000001688B5FE9000005C7000000000000000000000000000000000000001900000000hyprlock-0.9.1/README.md# hyprlock
Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility.
## Features
- Uses the ext-session-lock protocol
- Support for fractional-scale
- Fully GPU accelerated
- Multi-threaded resource acquisition
- Blurred screenshot as the background
- Native fingerprint support (using libfprint's dbus interface)
- Some of Hyprland's eyecandy: gradient borders, blur, animations, shadows, etc.
- and more...
## How it looks

## Docs / Configuration
[See the wiki](https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/)
## Arch install
```sh
pacman -S hyprlock # binary x86 tagged release
# or
yay -S hyprlock-git # compiles from latest source
```
## Building
### Deps
You need the following dependencies
- cairo
- hyprgraphics
- hyprland-protocols
- hyprlang
- hyprutils
- hyprwayland-scanner
- mesa (required is libgbm, libdrm and the opengl runtime)
- pam
- pango
- sdbus-cpp (>= 2.0.0)
- wayland-client
- wayland-protocols
- xkbcommon
Sometimes distro packages are missing required development files.
Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`.
### Building
Building:
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build
cmake --build ./build --config Release --target hyprlock -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
```
Installation:
```sh
sudo cmake --install build
```
0707010000000C000081A4000000000000000000000001688B5FE900000006000000000000000000000000000000000000001700000000hyprlock-0.9.1/VERSION0.9.1
0707010000000D000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001600000000hyprlock-0.9.1/assets0707010000000E000081A4000000000000000000000001688B5FE90000097E000000000000000000000000000000000000002300000000hyprlock-0.9.1/assets/example.conf# sample hyprlock.conf
# for more configuration options, refer https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock
#
# rendered text in all widgets supports pango markup (e.g. <b> or <i> tags)
# ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#general-remarks
#
# shortcuts to clear password buffer: ESC, Ctrl+U, Ctrl+Backspace
#
# you can get started by copying this config to ~/.config/hypr/hyprlock.conf
#
$font = Monospace
general {
hide_cursor = false
}
# uncomment to enable fingerprint authentication
# auth {
# fingerprint {
# enabled = true
# ready_message = Scan fingerprint to unlock
# present_message = Scanning...
# retry_delay = 250 # in milliseconds
# }
# }
animations {
enabled = true
bezier = linear, 1, 1, 0, 0
animation = fadeIn, 1, 5, linear
animation = fadeOut, 1, 5, linear
animation = inputFieldDots, 1, 2, linear
}
background {
monitor =
path = screenshot
blur_passes = 3
}
input-field {
monitor =
size = 20%, 5%
outline_thickness = 3
inner_color = rgba(0, 0, 0, 0.0) # no fill
outer_color = rgba(33ccffee) rgba(00ff99ee) 45deg
check_color = rgba(00ff99ee) rgba(ff6633ee) 120deg
fail_color = rgba(ff6633ee) rgba(ff0066ee) 40deg
font_color = rgb(143, 143, 143)
fade_on_empty = false
rounding = 15
font_family = $font
placeholder_text = Input password...
fail_text = $PAMFAIL
# uncomment to use a letter instead of a dot to indicate the typed password
# dots_text_format = *
# dots_size = 0.4
dots_spacing = 0.3
# uncomment to use an input indicator that does not show the password length (similar to swaylock's input indicator)
# hide_input = true
position = 0, -20
halign = center
valign = center
}
# TIME
label {
monitor =
text = $TIME # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#variable-substitution
font_size = 90
font_family = $font
position = -30, 0
halign = right
valign = top
}
# DATE
label {
monitor =
text = cmd[update:60000] date +"%A, %d %B %Y" # update every 60 seconds
font_size = 25
font_family = $font
position = -30, -150
halign = right
valign = top
}
label {
monitor =
text = $LAYOUT[en,ru]
font_size = 24
onclick = hyprctl switchxkblayout all next
position = 250, -20
halign = center
valign = center
}
0707010000000F000081A4000000000000000000000001688B5FE900000DAD000000000000000000000000000000000000001A00000000hyprlock-0.9.1/flake.lock{
"nodes": {
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1750621377,
"narHash": "sha256-8u6b5oAdX0rCuoR8wFenajBRmI+mzbpNig6hSCuWUzE=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "b3d628d01693fb9bb0a6690cd4e7b80abda04310",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1750371198,
"narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprlang",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1751061882,
"narHash": "sha256-g9n8Vrbx+2JYM170P9BbvGHN39Wlkr4U+V2WLHQsXL8=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "4737241eaf8a1e51671a2a088518071f9a265cf4",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1750371869,
"narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1751011381,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"hyprgraphics": "hyprgraphics",
"hyprlang": "hyprlang",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
07070100000010000081A4000000000000000000000001688B5FE9000006F4000000000000000000000000000000000000001900000000hyprlock-0.9.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";
hyprgraphics = {
url = "github:hyprwm/hyprgraphics";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprutils = {
url = "github:hyprwm/hyprutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
hyprlang = {
url = "github:hyprwm/hyprlang";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprwayland-scanner = {
url = "github:hyprwm/hyprwayland-scanner";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
};
outputs = {
self,
nixpkgs,
systems,
...
} @ inputs: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [default];
});
in {
overlays = import ./nix/overlays.nix {inherit inputs lib self;};
packages = eachSystem (system: {
default = self.packages.${system}.hyprlock;
inherit (pkgsFor.${system}) hyprlock;
});
homeManagerModules = {
default = self.homeManagerModules.hyprlock;
hyprlock = builtins.throw "hyprlock: the flake HM module has been removed. Use the module from Home Manager upstream.";
};
checks = eachSystem (system: self.packages.${system});
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}
07070100000011000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001300000000hyprlock-0.9.1/nix07070100000012000081A4000000000000000000000001688B5FE90000045E000000000000000000000000000000000000001F00000000hyprlock-0.9.1/nix/default.nix{
lib,
stdenv,
cmake,
pkg-config,
cairo,
libdrm,
libGL,
libxkbcommon,
libgbm,
hyprgraphics,
hyprlang,
hyprutils,
hyprwayland-scanner,
pam,
pango,
sdbus-cpp,
systemdLibs,
wayland,
wayland-protocols,
wayland-scanner,
version ? "git",
shortRev ? "",
}:
stdenv.mkDerivation {
pname = "hyprlock";
inherit version;
src = ../.;
nativeBuildInputs = [
cmake
pkg-config
hyprwayland-scanner
wayland-scanner
];
buildInputs = [
cairo
libdrm
libGL
libxkbcommon
libgbm
hyprgraphics
hyprlang
hyprutils
pam
pango
sdbus-cpp
systemdLibs
wayland
wayland-protocols
];
cmakeFlags = lib.mapAttrsToList lib.cmakeFeature {
HYPRLOCK_COMMIT = shortRev;
HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit)
};
meta = {
homepage = "https://github.com/hyprwm/hyprlock";
description = "A gpu-accelerated screen lock for Hyprland";
license = lib.licenses.bsd3;
platforms = lib.platforms.linux;
mainProgram = "hyprlock";
};
}
07070100000013000081A4000000000000000000000001688B5FE9000004ED000000000000000000000000000000000000002000000000hyprlock-0.9.1/nix/overlays.nix{
lib,
inputs,
self,
}: let
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in {
default = inputs.self.overlays.hyprlock;
hyprlock = lib.composeManyExtensions [
inputs.hyprgraphics.overlays.default
inputs.hyprlang.overlays.default
inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default
inputs.self.overlays.sdbuscpp
(final: prev: {
hyprlock = prev.callPackage ./default.nix {
stdenv = prev.gcc15Stdenv;
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
inherit (final) hyprlang;
shortRev = self.sourceInfo.shortRev or "dirty";
};
})
];
sdbuscpp = final: prev: {
sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: {
version = "2.0.0";
src = final.fetchFromGitHub {
owner = "Kistler-group";
repo = "sdbus-cpp";
rev = "refs/tags/v${self.version}";
hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g=";
};
});
};
}
07070100000014000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001300000000hyprlock-0.9.1/pam07070100000015000081A4000000000000000000000001688B5FE90000007E000000000000000000000000000000000000001C00000000hyprlock-0.9.1/pam/hyprlock# PAM configuration file for hyprlock
# the 'login' configuration file (see /etc/pam.d/login)
auth include login
07070100000016000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001900000000hyprlock-0.9.1/protocols07070100000017000081A4000000000000000000000001688B5FE900002585000000000000000000000000000000000000003800000000hyprlock-0.9.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>
07070100000018000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001300000000hyprlock-0.9.1/src07070100000019000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001800000000hyprlock-0.9.1/src/auth0707010000001A000081A4000000000000000000000001688B5FE900000D9F000000000000000000000000000000000000002100000000hyprlock-0.9.1/src/auth/Auth.cpp#include "Auth.hpp"
#include "Pam.hpp"
#include "Fingerprint.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "src/helpers/Log.hpp"
#include <hyprlang.hpp>
#include <memory>
CAuth::CAuth() {
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
if (*ENABLEPAM)
m_vImpls.emplace_back(makeShared<CPam>());
static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:enabled");
if (*ENABLEFINGERPRINT)
m_vImpls.emplace_back(makeShared<CFingerprint>());
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
}
void CAuth::start() {
for (const auto& i : m_vImpls) {
i->init();
}
}
void CAuth::submitInput(const std::string& input) {
for (const auto& i : m_vImpls) {
i->handleInput(input);
}
g_pHyprlock->clearPasswordBuffer();
}
bool CAuth::checkWaiting() {
return std::ranges::any_of(m_vImpls, [](const auto& i) { return i->checkWaiting(); });
}
const std::string& CAuth::getCurrentFailText() {
return m_sCurrentFail.failText;
}
std::optional<std::string> CAuth::getFailText(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastFailText();
}
return std::nullopt;
}
std::optional<std::string> CAuth::getPrompt(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastPrompt();
}
return std::nullopt;
}
size_t CAuth::getFailedAttempts() {
return m_sCurrentFail.failedAttempts;
}
SP<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i;
}
return nullptr;
}
void CAuth::terminate() {
for (const auto& i : m_vImpls) {
i->terminate();
}
}
static void unlockCallback(ASP<CTimer> self, void* data) {
g_pHyprlock->unlock();
}
void CAuth::enqueueUnlock() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), unlockCallback, nullptr);
}
static void passwordFailCallback(ASP<CTimer> self, void* data) {
g_pAuth->m_bDisplayFailText = true;
g_pHyprlock->enqueueForceUpdateTimers();
g_pHyprlock->renderAllOutputs();
}
static void displayFailTimeoutCallback(ASP<CTimer> self, void* data) {
if (g_pAuth->m_bDisplayFailText) {
g_pAuth->m_bDisplayFailText = false;
g_pHyprlock->renderAllOutputs();
}
}
void CAuth::enqueueFail(const std::string& failText, eAuthImplementations implType) {
static const auto FAILTIMEOUT = g_pConfigManager->getValue<Hyprlang::INT>("general:fail_timeout");
m_sCurrentFail.failText = failText;
m_sCurrentFail.failSource = implType;
m_sCurrentFail.failedAttempts++;
Debug::log(LOG, "Failed attempts: {}", m_sCurrentFail.failedAttempts);
if (m_resetDisplayFailTimer) {
m_resetDisplayFailTimer->cancel();
m_resetDisplayFailTimer.reset();
}
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordFailCallback, nullptr);
m_resetDisplayFailTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(*FAILTIMEOUT), displayFailTimeoutCallback, nullptr);
}
void CAuth::resetDisplayFail() {
g_pAuth->m_bDisplayFailText = false;
m_resetDisplayFailTimer->cancel();
m_resetDisplayFailTimer.reset();
}
0707010000001B000081A4000000000000000000000001688B5FE900000837000000000000000000000000000000000000002100000000hyprlock-0.9.1/src/auth/Auth.hpp#pragma once
#include <optional>
#include <vector>
#include "../defines.hpp"
#include "../core/Timer.hpp"
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
};
class IAuthImplementation {
public:
virtual ~IAuthImplementation() = default;
virtual eAuthImplementations getImplType() = 0;
virtual void init() = 0;
virtual void handleInput(const std::string& input) = 0;
virtual bool checkWaiting() = 0;
virtual std::optional<std::string> getLastFailText() = 0;
virtual std::optional<std::string> getLastPrompt() = 0;
virtual void terminate() = 0;
friend class CAuth;
};
class CAuth {
public:
CAuth();
void start();
void submitInput(const std::string& input);
bool checkWaiting();
const std::string& getCurrentFailText();
std::optional<std::string> getFailText(eAuthImplementations implType);
std::optional<std::string> getPrompt(eAuthImplementations implType);
size_t getFailedAttempts();
SP<IAuthImplementation> getImpl(eAuthImplementations implType);
void terminate();
void enqueueUnlock();
void enqueueFail(const std::string& failText, eAuthImplementations implType);
void resetDisplayFail();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
private:
struct {
std::string failText = "";
eAuthImplementations failSource = AUTH_IMPL_PAM;
size_t failedAttempts = 0;
} m_sCurrentFail;
std::vector<SP<IAuthImplementation>> m_vImpls;
ASP<CTimer> m_resetDisplayFailTimer;
};
inline UP<CAuth> g_pAuth;
0707010000001C000081A4000000000000000000000001688B5FE9000028D4000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/auth/Fingerprint.cpp#include "Fingerprint.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include <memory>
#include <unistd.h>
#include <pwd.h>
#include <cstring>
static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"};
static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"};
static const auto MANAGER = sdbus::ServiceName{"net.reactivated.Fprint.Manager"};
static const auto LOGIN_MANAGER = sdbus::ServiceName{"org.freedesktop.login1.Manager"};
enum MatchResult {
MATCH_INVALID = 0,
MATCH_NO_MATCH,
MATCH_MATCHED,
MATCH_RETRY,
MATCH_SWIPE_TOO_SHORT,
MATCH_FINGER_NOT_CENTERED,
MATCH_REMOVE_AND_RETRY,
MATCH_DISCONNECTED,
MATCH_UNKNOWN_ERROR,
};
static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MATCH_NO_MATCH},
{"verify-match", MATCH_MATCHED},
{"verify-retry-scan", MATCH_RETRY},
{"verify-swipe-too-short", MATCH_SWIPE_TOO_SHORT},
{"verify-finger-not-centered", MATCH_FINGER_NOT_CENTERED},
{"verify-remove-and-retry", MATCH_REMOVE_AND_RETRY},
{"verify-disconnected", MATCH_DISCONNECTED},
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
CFingerprint::CFingerprint() {
static const auto FINGERPRINTREADY = g_pConfigManager->getValue<Hyprlang::STRING>("auth:fingerprint:ready_message");
m_sFingerprintReady = *FINGERPRINTREADY;
static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue<Hyprlang::STRING>("auth:fingerprint:present_message");
m_sFingerprintPresent = *FINGERPRINTPRESENT;
}
CFingerprint::~CFingerprint() {
;
}
void CFingerprint::init() {
try {
m_sDBUSState.connection = sdbus::createSystemBusConnection();
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
} catch (sdbus::Error& e) {
Debug::log(ERR, "fprint: Failed to setup dbus ({})", e.what());
m_sDBUSState.connection.reset();
return;
}
m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::Variant preparingForSleep) {
if (e) {
Debug::log(WARN, "fprint: Failed getting value for PreparingForSleep: {}", e->what());
return;
}
m_sDBUSState.sleeping = preparingForSleep.get<bool>();
// When entering sleep, the wake signal will trigger startVerify().
if (m_sDBUSState.sleeping)
return;
startVerify();
});
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
m_sDBUSState.sleeping = start;
if (!m_sDBUSState.sleeping && !m_sDBUSState.verifying)
startVerify();
});
}
void CFingerprint::handleInput(const std::string& input) {
;
}
std::optional<std::string> CFingerprint::getLastFailText() {
if (!m_sFailureReason.empty())
return std::optional(m_sFailureReason);
return std::nullopt;
}
std::optional<std::string> CFingerprint::getLastPrompt() {
if (!m_sPrompt.empty())
return std::optional(m_sPrompt);
return std::nullopt;
}
bool CFingerprint::checkWaiting() {
return false;
}
void CFingerprint::terminate() {
if (!m_sDBUSState.abort)
releaseDevice();
}
std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
bool CFingerprint::createDeviceProxy() {
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
sdbus::ObjectPath path;
try {
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: couldn't connect to Fprint service ({})", e.what());
return false;
}
Debug::log(LOG, "fprint: using device path {}", path.c_str());
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, path);
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "fprint: finger selected: {}", finger); });
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); });
m_sDBUSState.device->uponSignal("PropertiesChanged")
.onInterface("org.freedesktop.DBus.Properties")
.call([this](const std::string& interface, const std::map<std::string, sdbus::Variant>& properties) {
if (interface != DEVICE || m_sDBUSState.done)
return;
try {
const auto presentVariant = properties.at("finger-present");
bool isPresent = presentVariant.get<bool>();
if (!isPresent)
return;
m_sPrompt = m_sFingerprintPresent;
g_pHyprlock->enqueueForceUpdateTimers();
} catch (std::out_of_range& e) {}
});
return true;
}
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
Debug::log(LOG, "fprint: handling status {}", result);
auto matchResult = s_mapStringToTestType[result];
bool authenticated = false;
bool retry = false;
if (m_sDBUSState.sleeping) {
stopVerify();
Debug::log(LOG, "fprint: device suspended");
return;
}
switch (matchResult) {
case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break;
case MATCH_NO_MATCH:
stopVerify();
if (m_sDBUSState.retries >= 3) {
m_sFailureReason = "Fingerprint auth disabled (too many failed attempts)";
} else {
done = false;
static const auto RETRYDELAY = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:retry_delay");
g_pHyprlock->addTimer(std::chrono::milliseconds(*RETRYDELAY), [](ASP<CTimer> self, void* data) { ((CFingerprint*)data)->startVerify(true); }, this);
m_sFailureReason = "Fingerprint did not match";
}
break;
case MATCH_UNKNOWN_ERROR:
stopVerify();
m_sFailureReason = "Fingerprint auth disabled (unknown error)";
break;
case MATCH_MATCHED:
stopVerify();
authenticated = true;
g_pAuth->enqueueUnlock();
break;
case MATCH_RETRY:
retry = true;
m_sPrompt = "Please retry fingerprint scan";
break;
case MATCH_SWIPE_TOO_SHORT:
retry = true;
m_sPrompt = "Swipe too short - try again";
break;
case MATCH_FINGER_NOT_CENTERED:
retry = true;
m_sPrompt = "Finger not centered - try again";
break;
case MATCH_REMOVE_AND_RETRY:
retry = true;
m_sPrompt = "Remove your finger and try again";
break;
case MATCH_DISCONNECTED:
m_sFailureReason = "Fingerprint device disconnected";
m_sDBUSState.abort = true;
break;
}
if (!authenticated && !retry)
g_pAuth->enqueueFail(m_sFailureReason, AUTH_IMPL_FINGERPRINT);
else if (retry)
g_pHyprlock->enqueueForceUpdateTimers();
if (done || m_sDBUSState.abort)
m_sDBUSState.done = true;
}
void CFingerprint::claimDevice() {
const auto currentUser = ""; // Empty string means use the caller's id.
m_sDBUSState.device->callMethodAsync("Claim").onInterface(DEVICE).withArguments(currentUser).uponReplyInvoke([this](std::optional<sdbus::Error> e) {
if (e)
Debug::log(WARN, "fprint: could not claim device, {}", e->what());
else {
Debug::log(LOG, "fprint: claimed device");
startVerify();
}
});
}
void CFingerprint::startVerify(bool isRetry) {
m_sDBUSState.verifying = true;
if (!m_sDBUSState.device) {
if (!createDeviceProxy())
return;
claimDevice();
return;
}
auto finger = "any"; // Any finger.
m_sDBUSState.device->callMethodAsync("VerifyStart").onInterface(DEVICE).withArguments(finger).uponReplyInvoke([this, isRetry](std::optional<sdbus::Error> e) {
if (e) {
Debug::log(WARN, "fprint: could not start verifying, {}", e->what());
if (isRetry)
m_sFailureReason = "Fingerprint auth disabled (failed to restart)";
} else {
Debug::log(LOG, "fprint: started verifying");
if (isRetry) {
m_sDBUSState.retries++;
m_sPrompt = "Could not match fingerprint. Try again.";
} else
m_sPrompt = m_sFingerprintReady;
}
g_pHyprlock->enqueueForceUpdateTimers();
});
}
bool CFingerprint::stopVerify() {
m_sDBUSState.verifying = false;
if (!m_sDBUSState.device)
return false;
try {
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: could not stop verifying, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: stopped verification");
return true;
}
bool CFingerprint::releaseDevice() {
if (!m_sDBUSState.device)
return false;
try {
m_sDBUSState.device->callMethod("Release").onInterface(DEVICE);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: could not release device, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: released device");
return true;
}
0707010000001D000081A4000000000000000000000001688B5FE90000068F000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/auth/Fingerprint.hpp#pragma once
#include "Auth.hpp"
#include <memory>
#include <optional>
#include <string>
#include <sdbus-c++/sdbus-c++.h>
class CFingerprint : public IAuthImplementation {
public:
CFingerprint();
virtual ~CFingerprint();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_FINGERPRINT;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
std::shared_ptr<sdbus::IConnection> getConnection();
private:
struct SDBUSState {
std::shared_ptr<sdbus::IConnection> connection;
std::unique_ptr<sdbus::IProxy> login;
std::unique_ptr<sdbus::IProxy> device;
bool abort = false;
bool done = false;
int retries = 0;
bool sleeping = false;
bool verifying = false;
} m_sDBUSState;
std::string m_sFingerprintReady;
std::string m_sFingerprintPresent;
std::string m_sPrompt{""};
std::string m_sFailureReason{""};
void handleVerifyStatus(const std::string& result, const bool done);
bool createDeviceProxy();
void claimDevice();
void startVerify(bool isRetry = false);
bool stopVerify();
bool releaseDevice();
};
0707010000001E000081A4000000000000000000000001688B5FE9000019FF000000000000000000000000000000000000002000000000hyprlock-0.9.1/src/auth/Pam.cpp#include "Pam.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../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 = (CPam::SPamConversationState*)appdata_ptr;
struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response));
bool initialPrompt = true;
for (int i = 0; i < num_msg; ++i) {
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON: {
const auto PROMPT = std::string(msg[i]->msg);
const auto PROMPTCHANGED = PROMPT != CONVERSATIONSTATE->prompt;
Debug::log(LOG, "PAM_PROMPT: {}", PROMPT);
if (PROMPTCHANGED)
g_pHyprlock->enqueueForceUpdateTimers();
// Some pam configurations ask for the password twice for whatever reason (Fedora su for example)
// When the prompt is the same as the last one, I guess our answer can be the same.
if (!initialPrompt && PROMPTCHANGED) {
CONVERSATIONSTATE->prompt = PROMPT;
CONVERSATIONSTATE->waitForInput();
}
// Needed for unlocks via SIGUSR1
if (g_pHyprlock->isUnlocked())
return PAM_CONV_ERR;
pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str());
initialPrompt = false;
} break;
case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break;
case PAM_TEXT_INFO:
Debug::log(LOG, "PAM: {}", msg[i]->msg);
// Targets this log from pam_faillock: https://github.com/linux-pam/linux-pam/blob/fa3295e079dbbc241906f29bde5fb71bc4172771/modules/pam_faillock/pam_faillock.c#L417
if (const auto MSG = std::string(msg[i]->msg); MSG.contains("left to unlock")) {
CONVERSATIONSTATE->failText = MSG;
CONVERSATIONSTATE->failTextFromPam = true;
}
break;
}
}
*resp = pamReply;
return PAM_SUCCESS;
}
CPam::CPam() {
static const auto PAMMODULE = g_pConfigManager->getValue<Hyprlang::STRING>("auth:pam:module");
m_sPamModule = *PAMMODULE;
if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) {
Debug::log(ERR, R"(Pam module "/etc/pam.d/{}" does not exist! Falling back to "/etc/pam.d/su")", m_sPamModule);
m_sPamModule = "su";
}
m_sConversationState.waitForInput = [this]() { this->waitForInput(); };
}
CPam::~CPam() {
;
}
void CPam::init() {
m_thread = std::thread([this]() {
while (true) {
resetConversation();
// Initial input
m_sConversationState.prompt = "Password: ";
waitForInput();
// For grace or SIGUSR1 unlocks
if (g_pHyprlock->isUnlocked())
return;
const auto AUTHENTICATED = auth();
// For SIGUSR1 unlocks
if (g_pHyprlock->isUnlocked())
return;
if (!AUTHENTICATED)
g_pAuth->enqueueFail(m_sConversationState.failText, AUTH_IMPL_PAM);
else {
g_pAuth->enqueueUnlock();
return;
}
}
});
}
bool CPam::auth() {
const pam_conv localConv = {.conv = conv, .appdata_ptr = (void*)&m_sConversationState};
pam_handle_t* handle = nullptr;
auto uidPassword = getpwuid(getuid());
RASSERT(uidPassword && uidPassword->pw_name, "Failed to get username (getpwuid)");
int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle);
if (ret != PAM_SUCCESS) {
m_sConversationState.failText = "pam_start failed";
Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule);
return false;
}
ret = pam_authenticate(handle, 0);
pam_end(handle, ret);
handle = nullptr;
m_sConversationState.waitingForPamAuth = false;
if (ret != PAM_SUCCESS) {
if (!m_sConversationState.failTextFromPam)
m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed";
Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule);
return false;
}
m_sConversationState.failText = "Successfully authenticated";
Debug::log(LOG, "auth: authenticated for {}", m_sPamModule);
return true;
}
void CPam::waitForInput() {
std::unique_lock<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 CPam::handleInput(const 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> CPam::getLastFailText() {
return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText);
}
std::optional<std::string> CPam::getLastPrompt() {
return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt);
}
bool CPam::checkWaiting() {
return m_bBlockInput || m_sConversationState.waitingForPamAuth;
}
void CPam::terminate() {
m_sConversationState.inputSubmittedCondition.notify_all();
if (m_thread.joinable())
m_thread.join();
}
void CPam::resetConversation() {
m_sConversationState.input = "";
m_sConversationState.waitingForPamAuth = false;
m_sConversationState.inputRequested = false;
m_sConversationState.failTextFromPam = false;
}
0707010000001F000081A4000000000000000000000001688B5FE9000005DC000000000000000000000000000000000000002000000000hyprlock-0.9.1/src/auth/Pam.hpp#pragma once
#include "Auth.hpp"
#include <optional>
#include <string>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <thread>
class CPam : public IAuthImplementation {
public:
struct SPamConversationState {
std::string input = "";
std::string prompt = "";
std::string failText = "";
std::mutex inputMutex;
std::condition_variable inputSubmittedCondition;
bool waitingForPamAuth = false;
bool inputRequested = false;
bool failTextFromPam = false;
std::function<void()> waitForInput = []() {};
};
CPam();
void waitForInput();
virtual ~CPam();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_PAM;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
private:
std::thread m_thread;
SPamConversationState m_sConversationState;
bool m_bBlockInput = true;
std::string m_sPamModule;
bool auth();
void resetConversation();
};
07070100000020000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001A00000000hyprlock-0.9.1/src/config07070100000021000081A4000000000000000000000001688B5FE900000EA5000000000000000000000000000000000000002F00000000hyprlock-0.9.1/src/config/ConfigDataValues.hpp#pragma once
#include "../helpers/Log.hpp"
#include "../helpers/Color.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/VarList.hpp>
#include <any>
#include <string>
#include <vector>
#include <cmath>
using namespace Hyprutils::String;
enum eConfigValueDataTypes {
CVD_TYPE_INVALID = -1,
CVD_TYPE_LAYOUT = 0,
CVD_TYPE_GRADIENT = 1,
};
class ICustomConfigValueData {
public:
virtual ~ICustomConfigValueData() = 0;
virtual eConfigValueDataTypes getDataType() = 0;
virtual std::string toString() = 0;
};
class CLayoutValueData : public ICustomConfigValueData {
public:
CLayoutValueData() = default;
virtual ~CLayoutValueData() {};
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_LAYOUT;
}
virtual std::string toString() {
return std::format("{}{},{}{}", m_vValues.x, (m_sIsRelative.x) ? "%" : "px", m_vValues.y, (m_sIsRelative.y) ? "%" : "px");
}
static CLayoutValueData* fromAnyPv(const std::any& v) {
RASSERT(v.type() == typeid(void*), "Invalid config value type");
const auto P = (CLayoutValueData*)std::any_cast<void*>(v);
RASSERT(P, "Empty config value");
return P;
}
Hyprutils::Math::Vector2D getAbsolute(const Hyprutils::Math::Vector2D& viewport) {
return {
(m_sIsRelative.x ? (m_vValues.x / 100) * viewport.x : m_vValues.x),
(m_sIsRelative.y ? (m_vValues.y / 100) * viewport.y : m_vValues.y),
};
}
Hyprutils::Math::Vector2D m_vValues;
struct {
bool x = false;
bool y = false;
} m_sIsRelative;
};
class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData() {};
CGradientValueData(CHyprColor col) {
m_vColors.push_back(col);
updateColorsOk();
};
virtual ~CGradientValueData() {};
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_GRADIENT;
}
void reset(CHyprColor col) {
m_vColors.clear();
m_vColors.emplace_back(col);
m_fAngle = 0;
updateColorsOk();
}
void updateColorsOk() {
m_vColorsOkLabA.clear();
for (auto& c : m_vColors) {
const auto OKLAB = c.asOkLab();
m_vColorsOkLabA.emplace_back(OKLAB.l);
m_vColorsOkLabA.emplace_back(OKLAB.a);
m_vColorsOkLabA.emplace_back(OKLAB.b);
m_vColorsOkLabA.emplace_back(c.a);
}
}
/* Vector containing the colors */
std::vector<CHyprColor> m_vColors;
/* Vector containing pure colors for shoving into opengl */
std::vector<float> m_vColorsOkLabA;
/* Float corresponding to the angle (rad) */
float m_fAngle = 0;
/* Whether this gradient stores a fallback value (not exlicitly set) */
bool m_bIsFallback = false;
//
bool operator==(const CGradientValueData& other) const {
if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle)
return false;
for (size_t i = 0; i < m_vColors.size(); ++i)
if (m_vColors[i] != other.m_vColors[i])
return false;
return true;
}
virtual std::string toString() {
std::string result;
for (auto& c : m_vColors) {
result += std::format("{:x} ", c.getAsHex());
}
result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI));
return result;
}
static CGradientValueData* fromAnyPv(const std::any& v) {
RASSERT(v.type() == typeid(void*), "Invalid config value type");
const auto P = (CGradientValueData*)std::any_cast<void*>(v);
RASSERT(P, "Empty config value");
return P;
}
};
07070100000022000081A4000000000000000000000001688B5FE900007D4D000000000000000000000000000000000000002C00000000hyprlock-0.9.1/src/config/ConfigManager.cpp#include "ConfigManager.hpp"
#include "ConfigDataValues.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../core/AnimationManager.hpp"
#include <hyprlang.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/path/Path.hpp>
#include <filesystem>
#include <glob.h>
#include <cstring>
#include <mutex>
using namespace Hyprutils::String;
using namespace Hyprutils::Animation;
ICustomConfigValueData::~ICustomConfigValueData() {
; // empty
}
static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleSource(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleBezier(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult configHandleLayoutOption(const char* v, void** data) {
const std::string VALUE = v;
Hyprlang::CParseResult result;
if (!*data)
*data = new CLayoutValueData();
const auto DATA = (CLayoutValueData*)(*data);
const auto SPLIT = VALUE.find(',');
if (SPLIT == std::string::npos) {
result.setError(std::format("expected two comma seperated values, got {}", VALUE).c_str());
return result;
}
auto lhs = VALUE.substr(0, SPLIT);
auto rhs = VALUE.substr(SPLIT + 1);
if (rhs.starts_with(" "))
rhs = rhs.substr(1);
if (lhs.contains(",") || rhs.contains(",")) {
result.setError(std::format("too many arguments in {}", VALUE).c_str());
return result;
}
if (lhs.ends_with("%")) {
DATA->m_sIsRelative.x = true;
lhs.pop_back();
}
if (rhs.ends_with("%")) {
DATA->m_sIsRelative.y = true;
rhs.pop_back();
}
DATA->m_vValues = Hyprutils::Math::Vector2D{std::stof(lhs), std::stof(rhs)};
return result;
}
static void configHandleLayoutOptionDestroy(void** data) {
if (*data)
delete reinterpret_cast<CLayoutValueData*>(*data);
}
static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) {
const std::string V = VALUE;
if (!*data)
*data = new CGradientValueData();
const auto DATA = reinterpret_cast<CGradientValueData*>(*data);
DATA->m_vColors.clear();
DATA->m_bIsFallback = false;
std::string parseError = "";
std::string rolling = V;
while (!rolling.empty()) {
const auto SPACEPOS = rolling.find(' ');
const bool LAST = SPACEPOS == std::string::npos;
std::string var = rolling.substr(0, SPACEPOS);
if (var.find("rgb") != std::string::npos) { // rgb(a)
const auto CLOSEPARENPOS = rolling.find(')');
if (CLOSEPARENPOS == std::string::npos || CLOSEPARENPOS + 1 >= rolling.length()) {
var = trim(rolling);
rolling.clear();
} else {
var = rolling.substr(0, CLOSEPARENPOS + 1);
rolling = trim(rolling.substr(CLOSEPARENPOS + 2));
}
} else if (var.find("deg") != std::string::npos) { // last arg
try {
DATA->m_fAngle = std::stoi(var.substr(0, var.find("deg"))) * (M_PI / 180.0); // radians
} catch (...) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V;
}
break;
} else // hex
rolling = trim(rolling.substr(LAST ? rolling.length() : SPACEPOS + 1));
if (DATA->m_vColors.size() >= 10) {
Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V);
parseError = "Error parsing gradient " + V + ": max colors is 10.";
break;
}
if (var.empty())
continue;
try {
DATA->m_vColors.emplace_back(configStringToInt(var));
} catch (std::exception& e) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V + ": " + e.what();
}
}
if (V.empty()) {
DATA->m_bIsFallback = true;
DATA->m_vColors.emplace_back(0); // transparent
}
if (DATA->m_vColors.size() == 0) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V + ": No colors?";
DATA->m_vColors.emplace_back(0); // transparent
}
DATA->updateColorsOk();
Hyprlang::CParseResult result;
if (!parseError.empty())
result.setError(parseError.c_str());
return result;
}
static void configHandleGradientDestroy(void** data) {
if (*data)
delete reinterpret_cast<CGradientValueData*>(*data);
}
static std::string getMainConfigPath() {
static const auto paths = Hyprutils::Path::findConfig("hyprlock");
if (paths.first.has_value())
return paths.first.value();
else
throw std::runtime_error("Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr.");
}
CConfigManager::CConfigManager(std::string configPath) :
m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = configPath.empty()}) {
configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath;
}
inline static constexpr auto GRADIENTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE {
return Hyprlang::CUSTOMTYPE{&configHandleGradientSet, configHandleGradientDestroy, default_value};
};
inline static constexpr auto LAYOUTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE {
return Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, default_value};
};
void CConfigManager::init() {
#define SHADOWABLE(name) \
m_config.addSpecialConfigValue(name, "shadow_size", Hyprlang::INT{3}); \
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0});
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
m_config.addConfigValue("general:screencopy_mode", Hyprlang::INT{0});
m_config.addConfigValue("general:fail_timeout", Hyprlang::INT{2000});
m_config.addConfigValue("auth:pam:enabled", Hyprlang::INT{1});
m_config.addConfigValue("auth:pam:module", Hyprlang::STRING{"hyprlock"});
m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0});
m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"});
m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"});
m_config.addConfigValue("auth:fingerprint:retry_delay", Hyprlang::INT{250});
m_config.addConfigValue("animations:enabled", Hyprlang::INT{1});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "color", Hyprlang::INT{0xFF111111});
m_config.addSpecialConfigValue("background", "blur_size", Hyprlang::INT{8});
m_config.addSpecialConfigValue("background", "blur_passes", Hyprlang::INT{0});
m_config.addSpecialConfigValue("background", "noise", Hyprlang::FLOAT{0.0117});
m_config.addSpecialConfigValue("background", "contrast", Hyprlang::FLOAT{0.8917});
m_config.addSpecialConfigValue("background", "brightness", Hyprlang::FLOAT{0.8172});
m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686});
m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05});
m_config.addSpecialConfigValue("background", "zindex", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("background", "reload_time", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("background", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "crossfade_time", Hyprlang::FLOAT{-1.0});
m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("shape", "size", LAYOUTCONFIG("100,100"));
m_config.addSpecialConfigValue("shape", "rounding", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "border_size", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "border_color", GRADIENTCONFIG("0xFF00CFE6"));
m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111});
m_config.addSpecialConfigValue("shape", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("shape", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("shape", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("shape", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
SHADOWABLE("shape");
CLICKABLE("shape");
m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "path", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "size", Hyprlang::INT{150});
m_config.addSpecialConfigValue("image", "rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("image", "border_size", Hyprlang::INT{4});
m_config.addSpecialConfigValue("image", "border_color", GRADIENTCONFIG("0xFFDDDDDD"));
m_config.addSpecialConfigValue("image", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("image", "reload_time", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0});
SHADOWABLE("image");
CLICKABLE("image");
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "size", LAYOUTCONFIG("400,90"));
m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD});
m_config.addSpecialConfigValue("input-field", "outer_color", GRADIENTCONFIG("0xFF111111"));
m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4});
m_config.addSpecialConfigValue("input-field", "dots_size", Hyprlang::FLOAT{0.25});
m_config.addSpecialConfigValue("input-field", "dots_center", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "dots_spacing", Hyprlang::FLOAT{0.2});
m_config.addSpecialConfigValue("input-field", "dots_rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "dots_text_format", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "fade_on_empty", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "fade_timeout", Hyprlang::INT{2000});
m_config.addSpecialConfigValue("input-field", "font_color", Hyprlang::INT{0xFF000000});
m_config.addSpecialConfigValue("input-field", "font_family", Hyprlang::STRING{"Sans"});
m_config.addSpecialConfigValue("input-field", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("input-field", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"});
m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "hide_input_base_color", Hyprlang::INT{0xEE00FF99});
m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88"));
m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222"));
m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"});
m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "invert_numlock", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "swap_font_color", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "zindex", Hyprlang::INT{0});
SHADOWABLE("input-field");
m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("label", "color", Hyprlang::INT{0xFFFFFFFF});
m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16});
m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"});
m_config.addSpecialConfigValue("label", "font_family", Hyprlang::STRING{"Sans"});
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0});
SHADOWABLE("label");
CLICKABLE("label");
m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
m_config.registerHandler(&::handleAnimation, "animation", {.allowFlags = false});
//
// Init Animations
//
m_AnimationTree.createNode("global");
// toplevel
m_AnimationTree.createNode("fade", "global");
m_AnimationTree.createNode("inputField", "global");
// inputField
m_AnimationTree.createNode("inputFieldColors", "inputField");
m_AnimationTree.createNode("inputFieldFade", "inputField");
m_AnimationTree.createNode("inputFieldWidth", "inputField");
m_AnimationTree.createNode("inputFieldDots", "inputField");
// fade
m_AnimationTree.createNode("fadeIn", "fade");
m_AnimationTree.createNode("fadeOut", "fade");
// set config for root node
m_AnimationTree.setConfigForNode("global", 1, 8.f, "default");
m_AnimationTree.setConfigForNode("inputFieldColors", 1, 8.f, "linear");
m_config.commence();
auto result = m_config.parse();
if (result.error)
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
#undef SHADOWABLE
#undef CLICKABLE
}
std::vector<CConfigManager::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()) \
}
#define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())}
//
auto keys = m_config.listKeysForSpecialCategory("background");
result.reserve(keys.size());
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type = "background",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())),
.values = {
{"path", m_config.getSpecialConfigValue("background", "path", k.c_str())},
{"color", m_config.getSpecialConfigValue("background", "color", k.c_str())},
{"blur_size", m_config.getSpecialConfigValue("background", "blur_size", k.c_str())},
{"blur_passes", m_config.getSpecialConfigValue("background", "blur_passes", k.c_str())},
{"noise", m_config.getSpecialConfigValue("background", "noise", k.c_str())},
{"contrast", m_config.getSpecialConfigValue("background", "contrast", k.c_str())},
{"vibrancy", m_config.getSpecialConfigValue("background", "vibrancy", k.c_str())},
{"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())},
{"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("background", "zindex", k.c_str())},
{"reload_time", m_config.getSpecialConfigValue("background", "reload_time", k.c_str())},
{"reload_cmd", m_config.getSpecialConfigValue("background", "reload_cmd", k.c_str())},
{"crossfade_time", m_config.getSpecialConfigValue("background", "crossfade_time", k.c_str())},
}
});
// clang-format on
}
//
keys = m_config.listKeysForSpecialCategory("shape");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type = "shape",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())),
.values = {
{"size", m_config.getSpecialConfigValue("shape", "size", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("shape", "rounding", k.c_str())},
{"border_size", m_config.getSpecialConfigValue("shape", "border_size", k.c_str())},
{"border_color", m_config.getSpecialConfigValue("shape", "border_color", k.c_str())},
{"color", m_config.getSpecialConfigValue("shape", "color", k.c_str())},
{"position", m_config.getSpecialConfigValue("shape", "position", k.c_str())},
{"halign", m_config.getSpecialConfigValue("shape", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("shape", "valign", k.c_str())},
{"rotate", m_config.getSpecialConfigValue("shape", "rotate", k.c_str())},
{"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())},
SHADOWABLE("shape"),
CLICKABLE("shape"),
}
});
// clang-format on
}
//
keys = m_config.listKeysForSpecialCategory("image");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type = "image",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("image", "monitor", k.c_str())),
.values = {
{"path", m_config.getSpecialConfigValue("image", "path", k.c_str())},
{"size", m_config.getSpecialConfigValue("image", "size", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("image", "rounding", k.c_str())},
{"border_size", m_config.getSpecialConfigValue("image", "border_size", k.c_str())},
{"border_color", m_config.getSpecialConfigValue("image", "border_color", k.c_str())},
{"position", m_config.getSpecialConfigValue("image", "position", k.c_str())},
{"halign", m_config.getSpecialConfigValue("image", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("image", "valign", k.c_str())},
{"rotate", m_config.getSpecialConfigValue("image", "rotate", k.c_str())},
{"reload_time", m_config.getSpecialConfigValue("image", "reload_time", k.c_str())},
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())},
SHADOWABLE("image"),
CLICKABLE("image"),
}
});
// clang-format on
}
keys = m_config.listKeysForSpecialCategory("input-field");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type = "input-field",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())),
.values = {
{"size", m_config.getSpecialConfigValue("input-field", "size", k.c_str())},
{"inner_color", m_config.getSpecialConfigValue("input-field", "inner_color", k.c_str())},
{"outer_color", m_config.getSpecialConfigValue("input-field", "outer_color", k.c_str())},
{"outline_thickness", m_config.getSpecialConfigValue("input-field", "outline_thickness", k.c_str())},
{"dots_size", m_config.getSpecialConfigValue("input-field", "dots_size", k.c_str())},
{"dots_spacing", m_config.getSpecialConfigValue("input-field", "dots_spacing", k.c_str())},
{"dots_center", m_config.getSpecialConfigValue("input-field", "dots_center", k.c_str())},
{"dots_rounding", m_config.getSpecialConfigValue("input-field", "dots_rounding", k.c_str())},
{"dots_text_format", m_config.getSpecialConfigValue("input-field", "dots_text_format", k.c_str())},
{"fade_on_empty", m_config.getSpecialConfigValue("input-field", "fade_on_empty", k.c_str())},
{"fade_timeout", m_config.getSpecialConfigValue("input-field", "fade_timeout", k.c_str())},
{"font_color", m_config.getSpecialConfigValue("input-field", "font_color", k.c_str())},
{"font_family", m_config.getSpecialConfigValue("input-field", "font_family", k.c_str())},
{"halign", m_config.getSpecialConfigValue("input-field", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("input-field", "valign", k.c_str())},
{"position", m_config.getSpecialConfigValue("input-field", "position", k.c_str())},
{"placeholder_text", m_config.getSpecialConfigValue("input-field", "placeholder_text", k.c_str())},
{"hide_input", m_config.getSpecialConfigValue("input-field", "hide_input", k.c_str())},
{"hide_input_base_color", m_config.getSpecialConfigValue("input-field", "hide_input_base_color", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("input-field", "rounding", k.c_str())},
{"check_color", m_config.getSpecialConfigValue("input-field", "check_color", k.c_str())},
{"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())},
{"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())},
{"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())},
{"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())},
{"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())},
{"invert_numlock", m_config.getSpecialConfigValue("input-field", "invert_numlock", k.c_str())},
{"swap_font_color", m_config.getSpecialConfigValue("input-field", "swap_font_color", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("input-field", "zindex", k.c_str())},
SHADOWABLE("input-field"),
}
});
// clang-format on
}
keys = m_config.listKeysForSpecialCategory("label");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type = "label",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())),
.values = {
{"position", m_config.getSpecialConfigValue("label", "position", k.c_str())},
{"color", m_config.getSpecialConfigValue("label", "color", k.c_str())},
{"font_size", m_config.getSpecialConfigValue("label", "font_size", k.c_str())},
{"font_family", m_config.getSpecialConfigValue("label", "font_family", k.c_str())},
{"text", m_config.getSpecialConfigValue("label", "text", k.c_str())},
{"halign", m_config.getSpecialConfigValue("label", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("label", "valign", k.c_str())},
{"rotate", m_config.getSpecialConfigValue("label", "rotate", k.c_str())},
{"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())},
SHADOWABLE("label"),
CLICKABLE("label"),
}
});
// clang-format on
}
return result;
}
std::optional<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 {};
}
std::optional<std::string> CConfigManager::handleBezier(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
std::string bezierName = ARGS[0];
if (ARGS[1] == "")
return "too few arguments";
float p1x = std::stof(ARGS[1]);
if (ARGS[2] == "")
return "too few arguments";
float p1y = std::stof(ARGS[2]);
if (ARGS[3] == "")
return "too few arguments";
float p2x = std::stof(ARGS[3]);
if (ARGS[4] == "")
return "too few arguments";
float p2y = std::stof(ARGS[4]);
if (ARGS[5] != "")
return "too many arguments";
g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y));
return {};
}
std::optional<std::string> CConfigManager::handleAnimation(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
const auto ANIMNAME = ARGS[0];
if (!m_AnimationTree.nodeExists(ANIMNAME))
return "no such animation";
// This helper casts strings like "1", "true", "off", "yes"... to int.
int64_t enabledInt = configStringToInt(ARGS[1]);
// Checking that the int is 1 or 0 because the helper can return integers out of range.
if (enabledInt > 1 || enabledInt < 0)
return "invalid animation on/off state";
if (!enabledInt) {
m_AnimationTree.setConfigForNode(ANIMNAME, 0, 1, "default");
return {};
}
int64_t speed = -1;
// speed
if (isNumber(ARGS[2], true)) {
speed = std::stof(ARGS[2]);
if (speed <= 0) {
speed = 1.f;
return "invalid speed";
}
} else {
speed = 10.f;
return "invalid speed";
}
std::string bezierName = ARGS[3];
// ARGS[4] (style) currently usused by hyprlock
m_AnimationTree.setConfigForNode(ANIMNAME, enabledInt, speed, bezierName, "");
if (!g_pAnimationManager->bezierExists(bezierName)) {
const auto PANIMNODE = m_AnimationTree.getConfig(ANIMNAME);
PANIMNODE->internalBezier = "default";
return "no such bezier";
}
return {};
}
07070100000023000081A4000000000000000000000001688B5FE9000004ED000000000000000000000000000000000000002C00000000hyprlock-0.9.1/src/config/ConfigManager.hpp#pragma once
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprlang.hpp>
#include <optional>
#include <vector>
#include <unordered_map>
#include "../defines.hpp"
class CConfigManager {
public:
CConfigManager(std::string configPath);
void init();
template <typename T>
Hyprlang::CSimpleConfigValue<T> getValue(const std::string& name) {
return Hyprlang::CSimpleConfigValue<T>(&m_config, name.c_str());
}
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::optional<std::string> handleBezier(const std::string&, const std::string&);
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
std::string configCurrentPath;
Hyprutils::Animation::CAnimationConfigTree m_AnimationTree;
private:
Hyprlang::CConfig m_config;
};
inline UP<CConfigManager> g_pConfigManager;
07070100000024000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001800000000hyprlock-0.9.1/src/core07070100000025000081A4000000000000000000000001688B5FE900001368000000000000000000000000000000000000002D00000000hyprlock-0.9.1/src/core/AnimationManager.cpp#include "AnimationManager.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../config/ConfigDataValues.hpp"
#include "../config/ConfigManager.hpp"
CHyprlockAnimationManager::CHyprlockAnimationManager() {
addBezierWithName("linear", {0, 0}, {1, 1});
}
template <Animable VarType>
void updateVariable(CAnimatedVariable<VarType>& av, const float POINTY, bool warp = false) {
if (warp || !av.enabled() || av.value() == av.goal()) {
av.warp(true, false);
return;
}
const auto DELTA = av.goal() - av.begun();
av.value() = av.begun() + DELTA * POINTY;
}
void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp = false) {
if (warp || !av.enabled() || av.value() == av.goal()) {
av.warp(true, false);
return;
}
// convert both to OkLab, then lerp that, and convert back.
// This is not as fast as just lerping rgb, but it's WAY more precise...
// Use the CHyprColor cache for OkLab
const auto& L1 = av.begun().asOkLab();
const auto& L2 = av.goal().asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)};
}
void updateGradientVariable(CAnimatedVariable<CGradientValueData>& av, const float POINTY, bool warp = false) {
if (warp || av.value() == av.goal()) {
av.warp(true, false);
return;
}
av.value().m_vColors.resize(av.goal().m_vColors.size(), av.goal().m_vColors.back());
for (size_t i = 0; i < av.value().m_vColors.size(); ++i) {
const CHyprColor& sourceCol = (i < av.begun().m_vColors.size()) ? av.begun().m_vColors[i] : av.begun().m_vColors.back();
const CHyprColor& targetCol = (i < av.goal().m_vColors.size()) ? av.goal().m_vColors[i] : av.goal().m_vColors.back();
const auto& L1 = sourceCol.asOkLab();
const auto& L2 = targetCol.asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value().m_vColors[i] = {lerped, lerp(sourceCol.a, targetCol.a, POINTY)};
}
if (av.begun().m_fAngle != av.goal().m_fAngle) {
const float DELTA = av.goal().m_fAngle - av.begun().m_fAngle;
av.value().m_fAngle = av.begun().m_fAngle + DELTA * POINTY;
}
av.value().updateColorsOk();
}
void CHyprlockAnimationManager::tick() {
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
if (!PAV || !PAV->ok())
continue;
const auto SPENT = PAV->getPercent();
const auto PBEZIER = getBezier(PAV->getBezierName());
const auto POINTY = PBEZIER->getYForPoint(SPENT);
const bool WARP = !*ANIMATIONSENABLED || SPENT >= 1.f;
switch (PAV->m_Type) {
case AVARTYPE_FLOAT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<float>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated float");
updateVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_VECTOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<Vector2D>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated Vector2D");
updateVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_COLOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CHyprColor>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CHyprColor");
updateColorVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_GRADIENT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CGradientValueData>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CGradientValueData");
updateGradientVariable(*pTypedAV, POINTY, WARP);
} break;
default: continue;
}
PAV->onUpdate();
}
tickDone();
}
void CHyprlockAnimationManager::scheduleTick() {
m_bTickScheduled = true;
}
void CHyprlockAnimationManager::onTicked() {
m_bTickScheduled = false;
}
07070100000026000081A4000000000000000000000001688B5FE900000434000000000000000000000000000000000000002D00000000hyprlock-0.9.1/src/core/AnimationManager.hpp#pragma once
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "../helpers/AnimatedVariable.hpp"
#include "../defines.hpp"
class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager {
public:
CHyprlockAnimationManager();
void tick();
virtual void scheduleTick();
virtual void onTicked();
using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig;
template <Animable VarType>
void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig) {
constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType<VarType>;
const auto PAV = makeShared<CAnimatedVariable<VarType>>();
PAV->create(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), PAV, v);
PAV->setConfig(pConfig);
pav = std::move(PAV);
}
bool m_bTickScheduled = false;
};
inline UP<CHyprlockAnimationManager> g_pAnimationManager;
07070100000027000081A4000000000000000000000001688B5FE9000002DA000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/core/CursorShape.cpp#include "CursorShape.hpp"
#include "Seat.hpp"
CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
if (!g_pSeatManager->m_pPointer)
return;
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
}
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
if (!g_pSeatManager->m_pPointer)
return;
if (!dev)
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
shapeChanged = true;
dev->sendSetShape(lastCursorSerial, shape);
}
void CCursorShape::hideCursor() {
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
}
07070100000028000081A4000000000000000000000001688B5FE9000001B0000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/core/CursorShape.hpp#pragma once
#include "../defines.hpp"
#include "cursor-shape-v1.hpp"
class CCursorShape {
public:
CCursorShape(SP<CCWpCursorShapeManagerV1> mgr);
void setShape(const wpCursorShapeDeviceV1Shape shape);
void hideCursor();
uint32_t lastCursorSerial = 0;
bool shapeChanged = false;
private:
SP<CCWpCursorShapeManagerV1> mgr = nullptr;
SP<CCWpCursorShapeDeviceV1> dev = nullptr;
};
07070100000029000081A4000000000000000000000001688B5FE900000B11000000000000000000000000000000000000002000000000hyprlock-0.9.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 == nullptr)
throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT");
eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
if (eglCreatePlatformWindowSurfaceEXT == nullptr)
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr);
EGLint matched = 0;
if (eglDisplay == EGL_NO_DISPLAY) {
Debug::log(CRIT, "Failed to create EGL display");
goto error;
}
if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) {
Debug::log(CRIT, "Failed to initialize EGL");
goto error;
}
if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) {
Debug::log(CRIT, "eglChooseConfig failed");
goto error;
}
if (matched == 0) {
Debug::log(CRIT, "Failed to match an EGL config");
goto error;
}
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs);
if (eglContext == EGL_NO_CONTEXT) {
Debug::log(CRIT, "Failed to create EGL context");
goto error;
}
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);
}
0707010000002A000081A4000000000000000000000001688B5FE9000001F6000000000000000000000000000000000000002000000000hyprlock-0.9.1/src/core/Egl.hpp#pragma once
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "../defines.hpp"
class CEGL {
public:
CEGL(wl_display*);
~CEGL();
EGLDisplay eglDisplay;
EGLConfig eglConfig;
EGLContext eglContext;
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT;
void makeCurrent(EGLSurface surf);
};
inline UP<CEGL> g_pEGL;
0707010000002B000081A4000000000000000000000001688B5FE900001418000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/core/LockSurface.cpp#include "LockSurface.hpp"
#include "hyprlock.hpp"
#include "Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../renderer/Renderer.hpp"
CSessionLockSurface::~CSessionLockSurface() {
if (eglWindow)
wl_egl_window_destroy(eglWindow);
}
CSessionLockSurface::CSessionLockSurface(const SP<COutput>& pOutput) : m_outputRef(pOutput), m_outputID(pOutput->m_ID) {
surface = makeShared<CCWlSurface>(g_pHyprlock->getCompositor()->sendCreateSurface());
RASSERT(surface, "Couldn't create wl_surface");
static const auto FRACTIONALSCALING = g_pConfigManager->getValue<Hyprlang::INT>("general:fractional_scaling");
const auto ENABLE_FSV1 = *FRACTIONALSCALING == 1 || /* auto enable */ (*FRACTIONALSCALING == 2);
const auto PFRACTIONALMGR = g_pHyprlock->getFractionalMgr();
const auto PVIEWPORTER = g_pHyprlock->getViewporter();
if (ENABLE_FSV1 && PFRACTIONALMGR && PVIEWPORTER) {
fractional = makeShared<CCWpFractionalScaleV1>(PFRACTIONALMGR->sendGetFractionalScale(surface->resource()));
fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) {
const bool SAMESCALE = fractionalScale == scale / 120.0;
fractionalScale = scale / 120.0;
Debug::log(LOG, "Got fractional scale: {:.1f}%", fractionalScale * 100.F);
if (!SAMESCALE && readyForFrame)
onScaleUpdate();
});
viewport = makeShared<CCWpViewport>(PVIEWPORTER->sendGetViewport(surface->resource()));
}
if (!PFRACTIONALMGR)
Debug::log(LOG, "No fractional-scale support! Oops, won't be able to scale!");
if (!PVIEWPORTER)
Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!");
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource()));
RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1");
lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) { configure({(double)width, (double)height}, serial); });
}
void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
Debug::log(LOG, "configure with serial {}", serial_);
const bool SAMESERIAL = serial == serial_;
const bool SAMESIZE = logicalSize == size_;
const bool SAMESCALE = appliedScale == fractionalScale;
const auto POUTPUT = m_outputRef.lock();
serial = serial_;
logicalSize = size_;
appliedScale = fractionalScale;
if (fractional) {
size = (size_ * fractionalScale).floor();
viewport->sendSetDestination(logicalSize.x, logicalSize.y);
surface->sendSetBufferScale(1);
} else {
size = size_ * POUTPUT->scale;
surface->sendSetBufferScale(POUTPUT->scale);
}
if (!SAMESERIAL)
lockSurface->sendAckConfigure(serial);
Debug::log(LOG, "Configuring surface for logical {} and pixel {}", logicalSize, size);
surface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
if (!eglWindow) {
eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y);
RASSERT(eglWindow, "Couldn't create eglWindow");
} else
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
if (!eglSurface) {
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
RASSERT(eglSurface, "Couldn't create eglSurface");
}
if (readyForFrame && !(SAMESIZE && SAMESCALE)) {
Debug::log(LOG, "output {} changed, reloading widgets!", POUTPUT->stringPort);
g_pRenderer->reconfigureWidgetsFor(POUTPUT->m_ID);
}
readyForFrame = true;
render();
}
void CSessionLockSurface::onScaleUpdate() {
configure(logicalSize, serial);
}
void CSessionLockSurface::render() {
if (frameCallback || !readyForFrame) {
needsFrame = true;
return;
}
g_pAnimationManager->tick();
const auto FEEDBACK = g_pRenderer->renderLock(*this);
frameCallback = makeShared<CCWlCallback>(surface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t frameTime) {
if (g_pHyprlock->m_bTerminate)
return;
if (Debug::verbose) {
const auto POUTPUT = m_outputRef.lock();
Debug::log(TRACE, "[{}] frame {}, Current fps: {:.2f}", POUTPUT->stringPort, m_frames, 1000.f / (frameTime - m_lastFrameTime));
}
m_lastFrameTime = frameTime;
m_frames++;
onCallback();
});
eglSwapBuffers(g_pEGL->eglDisplay, eglSurface);
needsFrame = FEEDBACK.needsFrame || g_pAnimationManager->shouldTickForNext();
}
void CSessionLockSurface::onCallback() {
frameCallback.reset();
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL) {
needsFrame = false;
render();
}
}
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
return surface;
}
0707010000002C000081A4000000000000000000000001688B5FE90000065E000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/core/LockSurface.hpp#pragma once
#include "../defines.hpp"
#include "wayland.hpp"
#include "ext-session-lock-v1.hpp"
#include "viewporter.hpp"
#include "fractional-scale-v1.hpp"
#include "../helpers/Math.hpp"
#include <wayland-egl.h>
#include <EGL/egl.h>
class COutput;
class CRenderer;
class CSessionLockSurface {
public:
CSessionLockSurface(const SP<COutput>& pOutput);
~CSessionLockSurface();
void configure(const Vector2D& size, uint32_t serial);
bool readyForFrame = false;
float fractionalScale = 1.0;
void render();
void onCallback();
void onScaleUpdate();
SP<CCWlSurface> getWlSurface();
private:
WP<COutput> m_outputRef;
OUTPUTID m_outputID = OUTPUT_INVALID;
SP<CCWlSurface> surface = nullptr;
SP<CCExtSessionLockSurfaceV1> lockSurface = nullptr;
uint32_t serial = 0;
wl_egl_window* eglWindow = nullptr;
Vector2D size;
Vector2D logicalSize;
float appliedScale;
EGLSurface eglSurface = nullptr;
SP<CCWpFractionalScaleV1> fractional = nullptr;
SP<CCWpViewport> viewport = nullptr;
bool needsFrame = false;
uint32_t m_lastFrameTime = 0;
uint32_t m_frames = 0;
// wayland callbacks
SP<CCWlCallback> frameCallback = nullptr;
friend class CRenderer;
friend class COutput;
};
0707010000002D000081A4000000000000000000000001688B5FE90000094A000000000000000000000000000000000000002300000000hyprlock-0.9.1/src/core/Output.cpp#include "Output.hpp"
#include "../helpers/Log.hpp"
#include "hyprlock.hpp"
void COutput::create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t _name) {
m_ID = _name;
m_wlOutput = pWlOutput;
m_self = pSelf;
m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) {
stringDesc = description ? std::string{description} : "";
Debug::log(LOG, "output {} description {}", m_ID, stringDesc);
});
m_wlOutput->setName([this](CCWlOutput* r, const char* name) {
stringName = std::string{name} + stringName;
stringPort = std::string{name};
Debug::log(LOG, "output {} name {}", name, name);
});
m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
m_wlOutput->setDone([this](CCWlOutput* r) {
done = true;
Debug::log(LOG, "output {} done", m_ID);
if (g_pHyprlock->m_lockAquired && !m_sessionLockSurface) {
Debug::log(LOG, "output {} creating a new lock surface", m_ID);
createSessionLockSurface();
}
});
m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
// handle portrait mode and flipped cases
if (transform % 2 == 1)
size = {height, width};
else
size = {width, height};
});
m_wlOutput->setGeometry(
[this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) {
transform = (wl_output_transform)transform_;
Debug::log(LOG, "output {} make {} model {}", m_ID, make ? make : "", model ? model : "");
});
}
void COutput::createSessionLockSurface() {
if (!m_self.valid()) {
Debug::log(ERR, "output {} dead??", m_ID);
return;
}
if (m_sessionLockSurface) {
Debug::log(ERR, "output {} already has a session lock surface", m_ID);
return;
}
if (size == Vector2D{0, 0}) {
Debug::log(WARN, "output {} refusing to create a lock surface with size 0x0", m_ID);
return;
}
m_sessionLockSurface = makeUnique<CSessionLockSurface>(m_self.lock());
}
Vector2D COutput::getViewport() const {
return (m_sessionLockSurface) ? m_sessionLockSurface->size : size;
}
0707010000002E000081A4000000000000000000000001688B5FE9000003A6000000000000000000000000000000000000002300000000hyprlock-0.9.1/src/core/Output.hpp#pragma once
#include "../defines.hpp"
#include "wayland.hpp"
#include "LockSurface.hpp"
class COutput {
public:
COutput() = default;
~COutput() = default;
void create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t name);
OUTPUTID m_ID = 0;
bool focused = false;
bool done = false;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
Vector2D size;
int scale = 1;
std::string stringName = "";
std::string stringPort = "";
std::string stringDesc = "";
UP<CSessionLockSurface> m_sessionLockSurface;
SP<CCWlOutput> m_wlOutput = nullptr;
WP<COutput> m_self;
void createSessionLockSurface();
Vector2D getViewport() const;
};
0707010000002F000081A4000000000000000000000001688B5FE900001D3E000000000000000000000000000000000000002100000000hyprlock-0.9.1/src/core/Seat.cpp#include "Seat.hpp"
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include <chrono>
#include <sys/mman.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
CSeatManager::~CSeatManager() {
if (m_pCursorShape && m_pCursorShape->shapeChanged)
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
if (m_pXKBState)
xkb_state_unref(m_pXKBState);
if (m_pXKBKeymap)
xkb_keymap_unref(m_pXKBKeymap);
if (m_pXKBContext)
xkb_context_unref(m_pXKBContext);
}
void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
m_pSeat = seat;
m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!m_pXKBContext)
Debug::log(ERR, "Failed to create xkb context");
m_pSeat->setCapabilities([this](CCWlSeat* r, wl_seat_capability caps) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());
static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (!*HIDECURSOR)
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);
if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
return;
if (!g_pHyprlock->isUnlocked() && g_pHyprlock->m_vLastEnterCoords.distance({wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}) > 5) {
Debug::log(LOG, "In grace and cursor moved more than 5px, unlocking!");
g_pHyprlock->unlock();
}
});
m_pPointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surf, wl_fixed_t surface_x, wl_fixed_t surface_y) {
if (!m_pCursorShape)
return;
m_pCursorShape->lastCursorSerial = serial;
if (*HIDECURSOR)
m_pCursorShape->hideCursor();
else
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (*HIDECURSOR)
return;
for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
if (!POUTPUT->m_sessionLockSurface)
continue;
const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
if (PWLSURFACE->resource() == surf)
g_pHyprlock->m_focusedOutput = POUTPUT;
}
});
m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });
m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
if (*HIDECURSOR)
return;
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
});
}
if (caps & WL_SEAT_CAPABILITY_TOUCH) {
m_pTouch = makeShared<CCWlTouch>(r->sendGetTouch());
m_pTouch->setDown([](CCWlTouch* r, uint32_t serial, uint32_t time, wl_proxy* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) {
g_pHyprlock->onClick(BTN_LEFT, true, {wl_fixed_to_double(x), wl_fixed_to_double(y)});
});
m_pTouch->setUp([](CCWlTouch* r, uint32_t serial, uint32_t time, int32_t id) { g_pHyprlock->onClick(BTN_LEFT, false, {0, 0}); });
};
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
m_pKeeb = makeShared<CCWlKeyboard>(r->sendGetKeyboard());
m_pKeeb->setKeymap([this](CCWlKeyboard*, wl_keyboard_keymap_format format, int32_t fd, uint32_t size) {
if (!m_pXKBContext)
return;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
Debug::log(ERR, "Could not recognise keymap format");
return;
}
const char* buf = (const char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
Debug::log(ERR, "Failed to mmap xkb keymap: {}", errno);
return;
}
m_pXKBKeymap = xkb_keymap_new_from_buffer(m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap((void*)buf, size);
close(fd);
if (!m_pXKBKeymap) {
Debug::log(ERR, "Failed to compile xkb keymap");
return;
}
m_pXKBState = xkb_state_new(m_pXKBKeymap);
if (!m_pXKBState) {
Debug::log(ERR, "Failed to create xkb state");
return;
}
const auto PCOMOPOSETABLE = xkb_compose_table_new_from_locale(m_pXKBContext, setlocale(LC_CTYPE, nullptr), XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!PCOMOPOSETABLE) {
Debug::log(ERR, "Failed to create xkb compose table");
return;
}
m_pXKBComposeState = xkb_compose_state_new(PCOMOPOSETABLE, XKB_COMPOSE_STATE_NO_FLAGS);
});
m_pKeeb->setKey([](CCWlKeyboard* r, uint32_t serial, uint32_t time, uint32_t key, wl_keyboard_key_state state) {
g_pHyprlock->onKey(key, state == WL_KEYBOARD_KEY_STATE_PRESSED);
});
m_pKeeb->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
if (!m_pXKBState)
return;
if (group != g_pHyprlock->m_uiActiveLayout) {
g_pHyprlock->m_uiActiveLayout = group;
for (auto& t : g_pHyprlock->getTimers()) {
if (t->canForceUpdate()) {
t->call(t);
t->cancel();
}
}
}
xkb_state_update_mask(m_pXKBState, mods_depressed, mods_latched, mods_locked, 0, 0, group);
g_pHyprlock->m_bCapsLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
g_pHyprlock->m_bNumLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED);
});
m_pKeeb->setRepeatInfo([](CCWlKeyboard* r, int32_t rate, int32_t delay) {
g_pHyprlock->m_iKeebRepeatRate = rate;
g_pHyprlock->m_iKeebRepeatDelay = delay;
});
}
});
m_pSeat->setName([](CCWlSeat* r, const char* name) { Debug::log(LOG, "Exposed seat name: {}", name ? name : "nullptr"); });
}
void CSeatManager::registerCursorShape(SP<CCWpCursorShapeManagerV1> shape) {
m_pCursorShape = makeUnique<CCursorShape>(shape);
}
bool CSeatManager::registered() {
return m_pSeat;
}
07070100000030000081A4000000000000000000000001688B5FE900000376000000000000000000000000000000000000002100000000hyprlock-0.9.1/src/core/Seat.hpp#pragma once
#include "../defines.hpp"
#include "CursorShape.hpp"
#include "wayland.hpp"
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
class CSeatManager {
public:
CSeatManager() = default;
~CSeatManager();
void registerSeat(SP<CCWlSeat> seat);
void registerCursorShape(SP<CCWpCursorShapeManagerV1> shape);
bool registered();
SP<CCWlKeyboard> m_pKeeb;
SP<CCWlPointer> m_pPointer;
SP<CCWlTouch> m_pTouch;
UP<CCursorShape> m_pCursorShape;
xkb_context* m_pXKBContext = nullptr;
xkb_keymap* m_pXKBKeymap = nullptr;
xkb_state* m_pXKBState = nullptr;
xkb_compose_state* m_pXKBComposeState = nullptr;
private:
SP<CCWlSeat> m_pSeat;
};
inline UP<CSeatManager> g_pSeatManager = makeUnique<CSeatManager>();
07070100000031000081A4000000000000000000000001688B5FE9000002E0000000000000000000000000000000000000002200000000hyprlock-0.9.1/src/core/Timer.cpp#include "Timer.hpp"
CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data_, bool force) :
cb(cb_), data(data_), allowForceUpdate(force) {
expires = std::chrono::system_clock::now() + timeout;
}
bool CTimer::passed() {
return std::chrono::system_clock::now() > expires;
}
void CTimer::cancel() {
wasCancelled = true;
}
bool CTimer::cancelled() {
return wasCancelled;
}
void CTimer::call(ASP<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;
}
07070100000032000081A4000000000000000000000001688B5FE9000002F5000000000000000000000000000000000000002200000000hyprlock-0.9.1/src/core/Timer.hpp#pragma once
#include <chrono>
#include <functional>
#include "../defines.hpp"
class CTimer {
public:
CTimer(std::chrono::system_clock::duration timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data_, bool force);
void cancel();
bool passed();
bool canForceUpdate();
float leftMs();
bool cancelled();
void call(ASP<CTimer> self);
private:
std::function<void(ASP<CTimer> self, void* data)> cb;
void* data = nullptr;
std::chrono::system_clock::time_point expires;
bool wasCancelled = false;
bool allowForceUpdate = false;
};
07070100000033000081A4000000000000000000000001688B5FE900007D2C000000000000000000000000000000000000002500000000hyprlock-0.9.1/src/core/hyprlock.cpp#include "hyprlock.hpp"
#include "AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "../renderer/Renderer.hpp"
#include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp"
#include "Egl.hpp"
#include <chrono>
#include <hyprutils/memory/UniquePtr.hpp>
#include <sys/wait.h>
#include <sys/poll.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <csignal>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <xf86drm.h>
#include <filesystem>
#include <fstream>
#include <algorithm>
#include <sdbus-c++/sdbus-c++.h>
#include <hyprutils/os/Process.hpp>
#include <malloc.h>
using namespace Hyprutils::OS;
static void setMallocThreshold() {
#ifdef M_TRIM_THRESHOLD
// The default is 128 pages,
// which is very large and can lead to a lot of memory used for no reason
// because trimming hasn't happened
static const int PAGESIZE = sysconf(_SC_PAGESIZE);
mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE);
#endif
}
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediateRender, const int graceSeconds) {
setMallocThreshold();
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
RASSERT(m_sWaylandState.display, "Couldn't connect to a wayland compositor");
g_pEGL = makeUnique<CEGL>(m_sWaylandState.display);
if (graceSeconds > 0)
m_tGraceEnds = std::chrono::system_clock::now() + std::chrono::seconds(graceSeconds);
else
m_tGraceEnds = std::chrono::system_clock::from_time_t(0);
static const auto IMMEDIATERENDER = g_pConfigManager->getValue<Hyprlang::INT>("general:immediate_render");
m_bImmediateRender = immediateRender || *IMMEDIATERENDER;
const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP");
const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""};
m_sCurrentDesktop = SZCURRENTD;
}
CHyprlock::~CHyprlock() {
if (dma.gbmDevice)
gbm_device_destroy(dma.gbmDevice);
}
static void registerSignalAction(int sig, void (*handler)(int), int sa_flags = 0) {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = sa_flags;
sigaction(sig, &sa, nullptr);
}
static void handleUnlockSignal(int sig) {
if (sig == SIGUSR1) {
Debug::log(LOG, "Unlocking with a SIGUSR1");
g_pAuth->enqueueUnlock();
}
}
static void handleForceUpdateSignal(int sig) {
if (sig == SIGUSR2) {
for (auto& t : g_pHyprlock->getTimers()) {
if (t->canForceUpdate()) {
t->call(t);
t->cancel();
}
}
}
}
static void handlePollTerminate(int sig) {
;
}
static char* gbm_find_render_node(drmDevice* device) {
drmDevice* devices[64];
char* render_node = nullptr;
int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0]));
for (int i = 0; i < n; ++i) {
drmDevice* dev = devices[i];
if (device && !drmDevicesEqual(device, dev)) {
continue;
}
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
render_node = strdup(dev->nodes[DRM_NODE_RENDER]);
break;
}
drmFreeDevices(devices, n);
return render_node;
}
gbm_device* CHyprlock::createGBMDevice(drmDevice* dev) {
char* renderNode = gbm_find_render_node(dev);
if (!renderNode) {
Debug::log(ERR, "[core] Couldn't find a render node");
return nullptr;
}
Debug::log(TRACE, "[core] createGBMDevice: render node {}", renderNode);
int fd = open(renderNode, O_RDWR | O_CLOEXEC);
if (fd < 0) {
Debug::log(ERR, "[core] couldn't open render node");
free(renderNode);
return nullptr;
}
free(renderNode);
return gbm_create_device(fd);
}
void CHyprlock::addDmabufListener() {
dma.linuxDmabufFeedback->setTrancheDone([this](CCZwpLinuxDmabufFeedbackV1* r) {
Debug::log(TRACE, "[core] dmabufFeedbackTrancheDone");
dma.deviceUsed = false;
});
dma.linuxDmabufFeedback->setTrancheFormats([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* indices) {
Debug::log(TRACE, "[core] dmabufFeedbackTrancheFormats");
if (!dma.deviceUsed || !dma.formatTable)
return;
struct fm_entry {
uint32_t format;
uint32_t padding;
uint64_t modifier;
};
// An entry in the table has to be 16 bytes long
static_assert(sizeof(fm_entry) == 16);
uint32_t n_modifiers = dma.formatTableSize / sizeof(fm_entry);
fm_entry* fm_entry = (struct fm_entry*)dma.formatTable;
uint16_t* idx;
for (idx = (uint16_t*)indices->data; (const char*)idx < (const char*)indices->data + indices->size; idx++) {
if (*idx >= n_modifiers)
continue;
Debug::log(TRACE, "GPU Reports supported format {:x} with modifier {:x}", (fm_entry + *idx)->format, (fm_entry + *idx)->modifier);
dma.dmabufMods.push_back({(fm_entry + *idx)->format, (fm_entry + *idx)->modifier});
}
});
dma.linuxDmabufFeedback->setTrancheTargetDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) {
Debug::log(TRACE, "[core] dmabufFeedbackTrancheTargetDevice");
dev_t device;
assert(device_arr->size == sizeof(device));
memcpy(&device, device_arr->data, sizeof(device));
drmDevice* drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0)
return;
if (dma.gbmDevice) {
drmDevice* drmDevRenderer = nullptr;
drmGetDevice2(gbm_device_get_fd(dma.gbmDevice), /* flags */ 0, &drmDevRenderer);
dma.deviceUsed = drmDevicesEqual(drmDevRenderer, drmDev);
} else {
dma.gbmDevice = createGBMDevice(drmDev);
dma.deviceUsed = dma.gbm;
}
});
dma.linuxDmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) {
Debug::log(TRACE, "[core] dmabufFeedbackDone");
if (dma.formatTable)
munmap(dma.formatTable, dma.formatTableSize);
dma.formatTable = nullptr;
dma.formatTableSize = 0;
});
dma.linuxDmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int fd, uint32_t size) {
Debug::log(TRACE, "[core] dmabufFeedbackFormatTable");
dma.dmabufMods.clear();
dma.formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (dma.formatTable == MAP_FAILED) {
Debug::log(ERR, "[core] format table failed to mmap");
dma.formatTable = nullptr;
dma.formatTableSize = 0;
return;
}
dma.formatTableSize = size;
});
dma.linuxDmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* device_arr) {
Debug::log(LOG, "[core] dmabufFeedbackMainDevice");
RASSERT(!dma.gbm, "double dmabuf feedback");
dev_t device;
assert(device_arr->size == sizeof(device));
memcpy(&device, device_arr->data, sizeof(device));
drmDevice* drmDev;
RASSERT(drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) == 0, "unable to open main device?");
dma.gbmDevice = createGBMDevice(drmDev);
drmFreeDevice(&drmDev);
});
dma.linuxDmabuf->setModifier([this](CCZwpLinuxDmabufV1* r, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) {
dma.dmabufMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo});
});
}
void CHyprlock::run() {
m_sWaylandState.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_sWaylandState.display));
m_sWaylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) {
const std::string IFACE = interface;
Debug::log(LOG, " | got iface: {} v{}", IFACE, version);
if (IFACE == zwp_linux_dmabuf_v1_interface.name) {
if (version < 4) {
Debug::log(ERR, "cannot use linux_dmabuf with ver < 4");
return;
}
dma.linuxDmabuf = makeShared<CCZwpLinuxDmabufV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwp_linux_dmabuf_v1_interface, 4));
dma.linuxDmabufFeedback = makeShared<CCZwpLinuxDmabufFeedbackV1>(dma.linuxDmabuf->sendGetDefaultFeedback());
addDmabufListener();
} else if (IFACE == wl_seat_interface.name) {
if (g_pSeatManager->registered()) {
Debug::log(WARN, "Hyprlock does not support multi-seat configurations. Only binding to the first seat.");
return;
}
g_pSeatManager->registerSeat(makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_seat_interface, 8)));
} else if (IFACE == ext_session_lock_manager_v1_interface.name)
m_sWaylandState.sessionLock =
makeShared<CCExtSessionLockManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &ext_session_lock_manager_v1_interface, 1));
else if (IFACE == wl_output_interface.name) {
const auto POUTPUT = makeShared<COutput>();
POUTPUT->create(POUTPUT, makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name);
m_vOutputs.emplace_back(POUTPUT);
} else if (IFACE == wp_cursor_shape_manager_v1_interface.name)
g_pSeatManager->registerCursorShape(
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_cursor_shape_manager_v1_interface, 1)));
else if (IFACE == wl_compositor_interface.name)
m_sWaylandState.compositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_compositor_interface, 4));
else if (IFACE == wp_fractional_scale_manager_v1_interface.name)
m_sWaylandState.fractional =
makeShared<CCWpFractionalScaleManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_fractional_scale_manager_v1_interface, 1));
else if (IFACE == wp_viewporter_interface.name)
m_sWaylandState.viewporter = makeShared<CCWpViewporter>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_viewporter_interface, 1));
else if (IFACE == zwlr_screencopy_manager_v1_interface.name)
m_sWaylandState.screencopy =
makeShared<CCZwlrScreencopyManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwlr_screencopy_manager_v1_interface, 3));
else if (IFACE == wl_shm_interface.name)
m_sWaylandState.shm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_shm_interface, 1));
else
return;
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
});
m_sWaylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) {
Debug::log(LOG, " | removed iface {}", name);
auto outputIt = std::ranges::find_if(m_vOutputs, [id = name](const auto& other) { return other->m_ID == id; });
if (outputIt != m_vOutputs.end()) {
g_pRenderer->removeWidgetsFor((*outputIt)->m_ID);
m_vOutputs.erase(outputIt);
}
});
wl_display_roundtrip(m_sWaylandState.display);
if (!m_sWaylandState.sessionLock) {
Debug::log(CRIT, "Couldn't bind to ext-session-lock-v1, does your compositor support it?");
exit(1);
}
// gather info about monitors
wl_display_roundtrip(m_sWaylandState.display);
g_pRenderer = makeUnique<CRenderer>();
g_pAuth = makeUnique<CAuth>();
g_pAuth->start();
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
if (!g_pHyprlock->m_bImmediateRender) {
// Gather background resources and screencopy frames before locking the screen.
// We need to do this because as soon as we lock the screen, workspaces frames can no longer be captured. It either won't work at all, or we will capture hyprlock itself.
// Bypass with --immediate-render (can cause the background first rendering a solid color and missing or inaccurate screencopy frames)
const auto MAXDELAYMS = 2000; // 2 Seconds
const auto STARTGATHERTP = std::chrono::system_clock::now();
int fdcount = 1;
pollfd pollfds[2];
pollfds[0] = {
.fd = wl_display_get_fd(m_sWaylandState.display),
.events = POLLIN,
};
if (g_pRenderer->asyncResourceGatherer->gatheredEventfd.isValid()) {
pollfds[1] = {
.fd = g_pRenderer->asyncResourceGatherer->gatheredEventfd.get(),
.events = POLLIN,
};
fdcount++;
}
while (!g_pRenderer->asyncResourceGatherer->gathered) {
wl_display_flush(m_sWaylandState.display);
if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(m_sWaylandState.display);
continue;
}
wl_display_read_events(m_sWaylandState.display);
wl_display_dispatch_pending(m_sWaylandState.display);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
wl_display_dispatch(m_sWaylandState.display);
}
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) {
Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS);
break;
}
}
Debug::log(LOG, "Resources gathered after {} milliseconds",
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count());
}
// Failed to lock the session
if (!acquireSessionLock()) {
m_sLoopState.timerEvent = true;
m_sLoopState.timerCV.notify_all();
g_pRenderer->asyncResourceGatherer->notify();
g_pRenderer->asyncResourceGatherer->await();
g_pAuth->terminate();
exit(1);
}
const auto fingerprintAuth = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT);
const auto dbusConn = (fingerprintAuth) ? ((CFingerprint*)fingerprintAuth.get())->getConnection() : nullptr;
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
registerSignalAction(SIGUSR2, handleForceUpdateSignal);
registerSignalAction(SIGRTMIN, handlePollTerminate);
pollfd pollfds[2];
pollfds[0] = {
.fd = wl_display_get_fd(m_sWaylandState.display),
.events = POLLIN,
};
if (dbusConn) {
pollfds[1] = {
.fd = dbusConn->getEventLoopPollData().fd,
.events = POLLIN,
};
}
size_t fdcount = dbusConn ? 2 : 1;
std::thread pollThr([this, &pollfds, fdcount]() {
while (!m_bTerminate) {
bool preparedToRead = wl_display_prepare_read(m_sWaylandState.display) == 0;
int events = 0;
if (preparedToRead) {
events = poll(pollfds, fdcount, 5000);
if (events < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(m_sWaylandState.display);
continue;
}
for (size_t i = 0; i < fdcount; ++i) {
RASSERT(!(pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i);
}
wl_display_read_events(m_sWaylandState.display);
m_sLoopState.wlDispatched = false;
}
if (events > 0 || !preparedToRead) {
Debug::log(TRACE, "[core] got poll event");
std::unique_lock lk(m_sLoopState.eventLoopMutex);
m_sLoopState.event = true;
m_sLoopState.loopCV.notify_all();
m_sLoopState.wlDispatchCV.wait_for(lk, std::chrono::milliseconds(100), [this] { return m_sLoopState.wlDispatched; });
}
}
});
std::thread timersThr([this]() {
while (!m_bTerminate) {
// calc nearest thing
m_sLoopState.timersMutex.lock();
float least = 10000;
for (auto& t : m_vTimers) {
const auto TIME = std::clamp(t->leftMs(), 1.f, INFINITY);
least = std::min(TIME, least);
}
m_sLoopState.timersMutex.unlock();
std::unique_lock lk(m_sLoopState.timerRequestMutex);
m_sLoopState.timerCV.wait_for(lk, std::chrono::milliseconds((int)least + 1), [this] { return m_sLoopState.timerEvent; });
m_sLoopState.timerEvent = false;
// notify main
std::lock_guard<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
g_pRenderer->startFadeIn();
while (!m_bTerminate) {
std::unique_lock lk(m_sLoopState.eventRequestMutex);
if (!m_sLoopState.event)
m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; });
if (m_bTerminate)
break;
std::lock_guard<std::mutex> lg(m_sLoopState.eventLoopMutex);
m_sLoopState.event = false;
wl_display_dispatch_pending(m_sWaylandState.display);
wl_display_flush(m_sWaylandState.display);
m_sLoopState.wlDispatched = true;
m_sLoopState.wlDispatchCV.notify_all();
if (pollfds[1].revents & POLLIN /* dbus */) {
while (dbusConn && dbusConn->processPendingEvent()) {
;
}
}
// do timers
m_sLoopState.timersMutex.lock();
auto timerscpy = m_vTimers;
m_sLoopState.timersMutex.unlock();
std::vector<ASP<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();
}
const auto DPY = m_sWaylandState.display;
m_sLoopState.timerEvent = true;
m_sLoopState.timerCV.notify_all();
g_pRenderer->asyncResourceGatherer->notify();
g_pRenderer->asyncResourceGatherer->await();
m_sWaylandState = {};
dma = {};
m_vOutputs.clear();
g_pEGL.reset();
g_pRenderer.reset();
g_pSeatManager.reset();
wl_display_disconnect(DPY);
pthread_kill(pollThr.native_handle(), SIGRTMIN);
g_pAuth->terminate();
// wait for threads to exit cleanly to avoid a coredump
pollThr.join();
timersThr.join();
Debug::log(LOG, "Reached the end, exiting");
}
void CHyprlock::unlock() {
if (!m_bLocked) {
Debug::log(WARN, "Unlock called, but not locked yet. This can happen when dpms is off during the grace period.");
return;
}
g_pRenderer->startFadeOut(true);
renderAllOutputs();
}
bool CHyprlock::isUnlocked() {
return !m_bLocked;
}
void CHyprlock::clearPasswordBuffer() {
if (m_sPasswordState.passBuffer.empty())
return;
m_sPasswordState.passBuffer = "";
renderAllOutputs();
}
void CHyprlock::renderOutput(const std::string& stringPort) {
const auto MON = std::ranges::find_if(m_vOutputs, [stringPort](const auto& other) { return other->stringPort == stringPort; });
if (MON == m_vOutputs.end() || !*MON)
return;
const auto& PMONITOR = *MON;
if (!PMONITOR->m_sessionLockSurface)
return;
PMONITOR->m_sessionLockSurface->render();
}
void CHyprlock::renderAllOutputs() {
for (auto& o : m_vOutputs) {
if (!o->m_sessionLockSurface)
continue;
o->m_sessionLockSurface->render();
}
}
void CHyprlock::startKeyRepeat(xkb_keysym_t sym) {
if (m_pKeyRepeatTimer) {
m_pKeyRepeatTimer->cancel();
m_pKeyRepeatTimer.reset();
}
if (g_pSeatManager->m_pXKBComposeState)
xkb_compose_state_reset(g_pSeatManager->m_pXKBComposeState);
if (m_iKeebRepeatDelay <= 0)
return;
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatDelay), [sym](ASP<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
}
void CHyprlock::repeatKey(xkb_keysym_t sym) {
if (m_iKeebRepeatRate <= 0)
return;
handleKeySym(sym, false);
// This condition is for backspace and delete keys, but should also be ok for other keysyms since our buffer won't be empty anyways
if (bool CONTINUE = m_sPasswordState.passBuffer.length() > 0; CONTINUE)
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatRate), [sym](ASP<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
renderAllOutputs();
}
void CHyprlock::onKey(uint32_t key, bool down) {
if (isUnlocked())
return;
if (down && std::chrono::system_clock::now() < m_tGraceEnds) {
unlock();
return;
}
if (down && std::ranges::find(m_vPressedKeys, key) != m_vPressedKeys.end()) {
Debug::log(ERR, "Invalid key down event (key already pressed?)");
return;
} else if (!down && std::ranges::find(m_vPressedKeys, key) == m_vPressedKeys.end()) {
Debug::log(ERR, "Invalid key down event (stray release event?)");
return;
}
if (down)
m_vPressedKeys.push_back(key);
else {
std::erase(m_vPressedKeys, key);
if (m_pKeyRepeatTimer) {
m_pKeyRepeatTimer->cancel();
m_pKeyRepeatTimer.reset();
}
}
if (g_pAuth->checkWaiting()) {
renderAllOutputs();
return;
}
if (g_pAuth->m_bDisplayFailText)
g_pAuth->resetDisplayFail();
if (down) {
m_bCapsLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
m_bNumLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED);
m_bCtrl = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE);
const auto SYM = xkb_state_key_get_one_sym(g_pSeatManager->m_pXKBState, key + 8);
enum xkb_compose_status composeStatus = XKB_COMPOSE_NOTHING;
if (g_pSeatManager->m_pXKBComposeState) {
xkb_compose_state_feed(g_pSeatManager->m_pXKBComposeState, SYM);
composeStatus = xkb_compose_state_get_status(g_pSeatManager->m_pXKBComposeState);
}
handleKeySym(SYM, composeStatus == XKB_COMPOSE_COMPOSED);
if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) // keys allowed to repeat
startKeyRepeat(SYM);
} else if (g_pSeatManager->m_pXKBComposeState && xkb_compose_state_get_status(g_pSeatManager->m_pXKBComposeState) == XKB_COMPOSE_COMPOSED)
xkb_compose_state_reset(g_pSeatManager->m_pXKBComposeState);
renderAllOutputs();
}
void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
const auto SYM = sym;
if (SYM == XKB_KEY_Escape || (m_bCtrl && (SYM == XKB_KEY_u || SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_a))) {
Debug::log(LOG, "Clearing password buffer");
m_sPasswordState.passBuffer = "";
} else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) {
Debug::log(LOG, "Authenticating");
static const auto IGNOREEMPTY = g_pConfigManager->getValue<Hyprlang::INT>("general:ignore_empty_input");
if (m_sPasswordState.passBuffer.empty() && *IGNOREEMPTY) {
Debug::log(LOG, "Ignoring empty input");
return;
}
g_pAuth->submitInput(m_sPasswordState.passBuffer);
} else if (SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_Delete) {
if (m_sPasswordState.passBuffer.length() > 0) {
// handle utf-8
while ((m_sPasswordState.passBuffer.back() & 0xc0) == 0x80)
m_sPasswordState.passBuffer.pop_back();
m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1);
}
} else if (SYM == XKB_KEY_Caps_Lock) {
m_bCapsLock = !m_bCapsLock;
} else if (SYM == XKB_KEY_Num_Lock) {
m_bNumLock = !m_bNumLock;
} else {
char buf[16] = {0};
int len = (composed) ? xkb_compose_state_get_utf8(g_pSeatManager->m_pXKBComposeState, buf, sizeof(buf)) /* nullbyte */ + 1 :
xkb_keysym_to_utf8(SYM, buf, sizeof(buf)) /* already includes a nullbyte */;
if (len > 1)
m_sPasswordState.passBuffer += std::string{buf, len - 1};
}
}
void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (!down)
return;
if (!m_focusedOutput.lock())
return;
// TODO: add the UNLIKELY marco from Hyprland
if (!m_focusedOutput->m_sessionLockSurface)
return;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
if (widget->containsPoint(SCALEDPOS))
widget->onClick(button, down, pos);
}
}
void CHyprlock::onHover(const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;
if (!m_focusedOutput->m_sessionLockSurface)
return;
bool outputNeedsRedraw = false;
bool cursorChanged = false;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
const bool HOVERED = widget->isHovered();
if (CONTAINSPOINT) {
if (!HOVERED) {
widget->setHover(true);
widget->onHover(pos);
outputNeedsRedraw = true;
}
if (!cursorChanged)
cursorChanged = true;
} else if (HOVERED) {
widget->setHover(false);
outputNeedsRedraw = true;
}
}
if (!cursorChanged)
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
if (outputNeedsRedraw)
m_focusedOutput->m_sessionLockSurface->render();
}
bool CHyprlock::acquireSessionLock() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
if (!m_sLockState.lock) {
Debug::log(ERR, "Failed to create a lock object!");
return false;
}
m_sLockState.lock->setLocked([this](CCExtSessionLockV1* r) { onLockLocked(); });
m_sLockState.lock->setFinished([this](CCExtSessionLockV1* r) { onLockFinished(); });
// roundtrip in case the compositor sends `finished` right away
wl_display_roundtrip(m_sWaylandState.display);
// recieved finished right away (probably already locked)
if (m_bTerminate)
return false;
m_lockAquired = true;
// create a session lock surface for exiting outputs
for (auto& o : m_vOutputs) {
if (!o->done)
continue;
o->createSessionLockSurface();
}
return true;
}
void CHyprlock::releaseSessionLock() {
Debug::log(LOG, "Unlocking session");
if (m_bTerminate) {
Debug::log(ERR, "Unlock already happend?");
return;
}
if (!m_sLockState.lock) {
Debug::log(ERR, "Unlock without a lock object!");
return;
}
if (!m_bLocked) {
// Would be a protocol error to allow this
Debug::log(ERR, "Trying to unlock the session, but never recieved the locked event!");
return;
}
m_sLockState.lock->sendUnlockAndDestroy();
m_sLockState.lock = nullptr;
Debug::log(LOG, "Unlocked, exiting!");
m_bTerminate = true;
m_bLocked = false;
wl_display_roundtrip(m_sWaylandState.display);
}
void CHyprlock::onLockLocked() {
Debug::log(LOG, "onLockLocked called");
m_bLocked = true;
}
void CHyprlock::onLockFinished() {
Debug::log(LOG, "onLockFinished called. Seems we got yeeten. Is another lockscreen running?");
if (!m_sLockState.lock) {
Debug::log(ERR, "onLockFinished without a lock object!");
return;
}
if (m_bLocked)
// The `finished` event specifies that whenever the `locked` event has been recieved and the compositor sends `finished`,
// `unlock_and_destroy` should be called by the client.
// This does not mean the session gets unlocked! That is ultimately the responsiblity of the compositor.
m_sLockState.lock->sendUnlockAndDestroy();
else
m_sLockState.lock.reset();
m_sLockState.lock = nullptr;
m_bTerminate = true;
}
SP<CCExtSessionLockManagerV1> CHyprlock::getSessionLockMgr() {
return m_sWaylandState.sessionLock;
}
SP<CCExtSessionLockV1> CHyprlock::getSessionLock() {
return m_sLockState.lock;
}
SP<CCWlCompositor> CHyprlock::getCompositor() {
return m_sWaylandState.compositor;
}
wl_display* CHyprlock::getDisplay() {
return m_sWaylandState.display;
}
SP<CCWpFractionalScaleManagerV1> CHyprlock::getFractionalMgr() {
return m_sWaylandState.fractional;
}
SP<CCWpViewporter> CHyprlock::getViewporter() {
return m_sWaylandState.viewporter;
}
size_t CHyprlock::getPasswordBufferLen() {
return m_sPasswordState.passBuffer.length();
}
size_t CHyprlock::getPasswordBufferDisplayLen() {
// Counts utf-8 codepoints in the buffer. A byte is counted if it does not match 0b10xxxxxx.
return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; });
}
ASP<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(ASP<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(makeAtomicShared<CTimer>(timeout, cb_, data, force));
m_sLoopState.timerEvent = true;
m_sLoopState.timerCV.notify_all();
return T;
}
std::vector<ASP<CTimer>> CHyprlock::getTimers() {
return m_vTimers;
}
void CHyprlock::enqueueForceUpdateTimers() {
addTimer(
std::chrono::milliseconds(1),
[](ASP<CTimer> self, void* data) {
for (auto& t : g_pHyprlock->getTimers()) {
if (t->canForceUpdate()) {
t->call(t);
t->cancel();
}
}
},
nullptr, false);
}
SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
return m_sWaylandState.screencopy;
}
SP<CCWlShm> CHyprlock::getShm() {
return m_sWaylandState.shm;
}
07070100000034000081A4000000000000000000000001688B5FE90000169D000000000000000000000000000000000000002500000000hyprlock-0.9.1/src/core/hyprlock.hpp#pragma once
#include "../defines.hpp"
#include "wayland.hpp"
#include "ext-session-lock-v1.hpp"
#include "fractional-scale-v1.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
#include "linux-dmabuf-v1.hpp"
#include "viewporter.hpp"
#include "Output.hpp"
#include "Seat.hpp"
#include "CursorShape.hpp"
#include "Timer.hpp"
#include <memory>
#include <vector>
#include <condition_variable>
#include <optional>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.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 immediateRender, const int gracePeriod);
~CHyprlock();
void run();
void unlock();
bool isUnlocked();
ASP<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data, bool force = false);
void enqueueForceUpdateTimers();
void onLockLocked();
void onLockFinished();
bool acquireSessionLock();
void releaseSessionLock();
void onKey(uint32_t key, bool down);
void onClick(uint32_t button, bool down, const Vector2D& pos);
void onHover(const Vector2D& pos);
void startKeyRepeat(xkb_keysym_t sym);
void repeatKey(xkb_keysym_t sym);
void handleKeySym(xkb_keysym_t sym, bool compose);
void onPasswordCheckTimer();
void clearPasswordBuffer();
bool passwordCheckWaiting();
std::optional<std::string> passwordLastFailReason();
void renderOutput(const std::string& stringPort);
void renderAllOutputs();
size_t getPasswordBufferLen();
size_t getPasswordBufferDisplayLen();
SP<CCExtSessionLockManagerV1> getSessionLockMgr();
SP<CCExtSessionLockV1> getSessionLock();
SP<CCWlCompositor> getCompositor();
wl_display* getDisplay();
SP<CCWpFractionalScaleManagerV1> getFractionalMgr();
SP<CCWpViewporter> getViewporter();
SP<CCZwlrScreencopyManagerV1> getScreencopy();
SP<CCWlShm> getShm();
int32_t m_iKeebRepeatRate = 25;
int32_t m_iKeebRepeatDelay = 600;
xkb_layout_index_t m_uiActiveLayout = 0;
bool m_bTerminate = false;
bool m_lockAquired = false;
bool m_bLocked = false;
bool m_bCapsLock = false;
bool m_bNumLock = false;
bool m_bCtrl = false;
bool m_bImmediateRender = false;
std::string m_sCurrentDesktop = "";
//
std::chrono::system_clock::time_point m_tGraceEnds;
Vector2D m_vLastEnterCoords = {};
WP<COutput> m_focusedOutput;
Vector2D m_vMouseLocation = {};
ASP<CTimer> m_pKeyRepeatTimer = nullptr;
std::vector<SP<COutput>> m_vOutputs;
std::vector<ASP<CTimer>> getTimers();
struct {
SP<CCZwpLinuxDmabufV1> linuxDmabuf = nullptr;
SP<CCZwpLinuxDmabufFeedbackV1> 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;
SP<CCWlRegistry> registry = nullptr;
SP<CCExtSessionLockManagerV1> sessionLock = nullptr;
SP<CCWlCompositor> compositor = nullptr;
SP<CCWpFractionalScaleManagerV1> fractional = nullptr;
SP<CCWpViewporter> viewporter = nullptr;
SP<CCZwlrScreencopyManagerV1> screencopy = nullptr;
SP<CCWlShm> shm = nullptr;
} m_sWaylandState;
void addDmabufListener();
struct {
SP<CCExtSessionLockV1> lock = nullptr;
} m_sLockState;
struct {
std::string passBuffer = "";
size_t failedAttempts = 0;
bool displayFailText = false;
} m_sPasswordState;
struct {
std::mutex timersMutex;
std::mutex eventRequestMutex;
std::mutex eventLoopMutex;
std::condition_variable loopCV;
bool event = false;
std::condition_variable wlDispatchCV;
bool wlDispatched = false;
std::condition_variable timerCV;
std::mutex timerRequestMutex;
bool timerEvent = false;
} m_sLoopState;
std::vector<ASP<CTimer>> m_vTimers;
std::vector<uint32_t> m_vPressedKeys;
};
inline UP<CHyprlock> g_pHyprlock;
07070100000035000081A4000000000000000000000001688B5FE9000001C4000000000000000000000000000000000000001F00000000hyprlock-0.9.1/src/defines.hpp#pragma once
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprgraphics/color/Color.hpp>
using namespace Hyprutils::Memory;
using namespace Hyprgraphics;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer
#define AWP CAtomicWeakPointer
typedef int64_t OUTPUTID;
constexpr OUTPUTID OUTPUT_INVALID = -1;
07070100000036000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001B00000000hyprlock-0.9.1/src/helpers07070100000037000081A4000000000000000000000001688B5FE900000775000000000000000000000000000000000000003000000000hyprlock-0.9.1/src/helpers/AnimatedVariable.hpp#pragma once
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "Color.hpp"
#include "Math.hpp"
#include "../defines.hpp"
#include "../config/ConfigDataValues.hpp"
enum eAnimatedVarType {
AVARTYPE_INVALID = -1,
AVARTYPE_FLOAT,
AVARTYPE_VECTOR,
AVARTYPE_COLOR,
AVARTYPE_GRADIENT
};
// Utility to bind a type with its corresponding eAnimatedVarType
template <class T>
// NOLINTNEXTLINE(readability-identifier-naming)
struct STypeToAnimatedVarType_t {
static constexpr eAnimatedVarType value = AVARTYPE_INVALID;
};
template <>
struct STypeToAnimatedVarType_t<float> {
static constexpr eAnimatedVarType value = AVARTYPE_FLOAT;
};
template <>
struct STypeToAnimatedVarType_t<Vector2D> {
static constexpr eAnimatedVarType value = AVARTYPE_VECTOR;
};
template <>
struct STypeToAnimatedVarType_t<CHyprColor> {
static constexpr eAnimatedVarType value = AVARTYPE_COLOR;
};
template <>
struct STypeToAnimatedVarType_t<CGradientValueData> {
static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT;
};
template <class T>
inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t<T>::value;
// Utility to define a concept as a list of possible type
template <class T, class... U>
concept OneOf = (... or std::same_as<T, U>);
// Concept to describe which type can be placed into CAnimatedVariable
// This is mainly to get better errors if we put a type that's not supported
// Otherwise template errors are ugly
template <class T>
concept Animable = OneOf<T, Vector2D, float, CHyprColor, CGradientValueData>;
struct SAnimationContext {};
template <Animable VarType>
using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable<VarType, SAnimationContext>;
template <Animable VarType>
using PHLANIMVAR = SP<CAnimatedVariable<VarType>>;
template <Animable VarType>
using PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;
07070100000038000081A4000000000000000000000001688B5FE9000005A9000000000000000000000000000000000000002500000000hyprlock-0.9.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)
CHyprColor::CHyprColor() {
;
}
CHyprColor::CHyprColor(float r_, float g_, float b_, float a_) : r(r_), g(g_), b(b_), a(a_) {
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab();
}
CHyprColor::CHyprColor(uint64_t hex) : r(RED(hex)), g(GREEN(hex)), b(BLUE(hex)), a(ALPHA(hex)) {
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab();
}
CHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) : a(a_) {
const auto SRGB = color.asRgb();
r = SRGB.r;
g = SRGB.g;
b = SRGB.b;
okLab = color.asOkLab();
}
uint32_t CHyprColor::getAsHex() const {
return ((uint32_t)(a * 255.f) * 0x1000000) + ((uint32_t)(r * 255.f) * 0x10000) + ((uint32_t)(g * 255.f) * 0x100) + ((uint32_t)(b * 255.f) * 0x1);
}
Hyprgraphics::CColor::SSRGB CHyprColor::asRGB() const {
return {.r = r, .g = g, .b = b};
}
Hyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const {
return okLab;
}
Hyprgraphics::CColor::SHSL CHyprColor::asHSL() const {
return Hyprgraphics::CColor(okLab).asHSL();
}
CHyprColor CHyprColor::stripA() const {
return {r, g, b, 1.F};
}
07070100000039000081A4000000000000000000000001688B5FE9000004D1000000000000000000000000000000000000002500000000hyprlock-0.9.1/src/helpers/Color.hpp#pragma once
#include <cstdint>
#include "../helpers/Log.hpp"
#include <hyprgraphics/color/Color.hpp>
class CHyprColor {
public:
CHyprColor();
CHyprColor(float r, float g, float b, float a);
CHyprColor(const Hyprgraphics::CColor& col, float a);
CHyprColor(uint64_t);
// AR32
uint32_t getAsHex() const;
Hyprgraphics::CColor::SSRGB asRGB() const;
Hyprgraphics::CColor::SOkLab asOkLab() const;
Hyprgraphics::CColor::SHSL asHSL() const;
CHyprColor stripA() const;
//
bool operator==(const CHyprColor& c2) const {
return c2.r == r && c2.g == g && c2.b == b && c2.a == a;
}
// stubs for the AnimationMgr
CHyprColor operator-(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: - is a STUB");
return {};
}
CHyprColor operator+(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: + is a STUB");
return {};
}
CHyprColor operator*(const float& c2) const {
RASSERT(false, "CHyprColor: * is a STUB");
return {};
}
double r = 0, g = 0, b = 0, a = 0;
private:
Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation
};
0707010000003A000081A4000000000000000000000001688B5FE900000866000000000000000000000000000000000000002300000000hyprlock-0.9.1/src/helpers/Log.hpp#pragma once
#include <format>
#include <string>
#include <print>
enum eLogLevel {
TRACE = 0,
INFO,
LOG,
WARN,
ERR,
CRIT,
NONE
};
#define RASSERT(expr, reason, ...) \
if (!(expr)) { \
Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \
std::format(reason, ##__VA_ARGS__), __LINE__, \
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
std::abort(); \
}
#define ASSERT(expr) RASSERT(expr, "?")
namespace Debug {
constexpr const char* logLevelString(eLogLevel level) {
switch (level) {
case TRACE: return "TRACE"; break;
case INFO: return "INFO"; break;
case LOG: return "LOG"; break;
case WARN: return "WARN"; break;
case ERR: return "ERR"; break;
case CRIT: return "CRITICAL"; break;
default: return "??";
}
}
inline bool quiet = false;
inline bool verbose = false;
template <typename... Args>
void log(eLogLevel level, const std::string& fmt, Args&&... args) {
if (!verbose && level == TRACE)
return;
if (quiet)
return;
if (level != NONE) {
std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...)));
}
}
};0707010000003B000081A4000000000000000000000001688B5FE90000050E000000000000000000000000000000000000002400000000hyprlock-0.9.1/src/helpers/Math.cpp#include "Math.hpp"
Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) {
switch (t) {
case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL;
case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180;
case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90;
case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270;
case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED;
case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180;
case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270;
case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90;
default: break;
}
return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL;
}
wl_output_transform invertTransform(wl_output_transform tr) {
if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED))
tr = (wl_output_transform)(tr ^ (int)WL_OUTPUT_TRANSFORM_180);
return tr;
}
0707010000003C000081A4000000000000000000000001688B5FE90000013C000000000000000000000000000000000000002400000000hyprlock-0.9.1/src/helpers/Math.hpp#pragma once
#include <wayland-client.h>
#include <hyprutils/math/Box.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/math/Mat3x3.hpp>
using namespace Hyprutils::Math;
eTransform wlTransformToHyprutils(wl_output_transform t);
wl_output_transform invertTransform(wl_output_transform tr);
0707010000003D000081A4000000000000000000000001688B5FE9000017ED000000000000000000000000000000000000002D00000000hyprlock-0.9.1/src/helpers/MiscFunctions.cpp#include <filesystem>
#include <algorithm>
#include <cmath>
#include <fcntl.h>
#include "MiscFunctions.hpp"
#include "Log.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
#include <unistd.h>
using namespace Hyprutils::String;
using namespace Hyprutils::OS;
std::string absolutePath(const std::string& rawpath, const std::string& currentDir) {
std::filesystem::path path(rawpath);
// Handling where rawpath starts with '~'
if (!rawpath.empty() && rawpath[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2);
}
// Handling e.g. ./, ../
if (path.is_relative()) {
return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path);
} else {
return std::filesystem::weakly_canonical(path);
}
}
int64_t configStringToInt(const std::string& VALUE) {
auto parseHex = [](const std::string& value) -> int64_t {
try {
size_t position;
auto result = stoll(value, &position, 16);
if (position == value.size())
return result;
} catch (const std::exception&) {}
throw std::invalid_argument("invalid hex " + value);
};
if (VALUE.starts_with("0x")) {
// Values with 0x are hex
return parseHex(VALUE);
} else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6));
// try doing it the comma way first
if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 3) {
// cool
std::string rolling = VALUEWITHOUTFUNC;
auto r = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto g = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto b = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
uint8_t a = 0;
try {
a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f);
} catch (std::exception& e) { throw std::invalid_argument("failed parsing " + VALUEWITHOUTFUNC); }
return (a * (Hyprlang::INT)0x1000000) + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b;
} else if (VALUEWITHOUTFUNC.length() == 8) {
const auto RGBA = parseHex(VALUEWITHOUTFUNC);
// now we need to RGBA -> ARGB. The config holds ARGB only.
return (RGBA >> 8) + (0x1000000 * (RGBA & 0xFF));
}
throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values");
} else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5));
// try doing it the comma way first
if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 2) {
// cool
std::string rolling = VALUEWITHOUTFUNC;
auto r = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto g = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto b = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
return (Hyprlang::INT)0xFF000000 + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b;
} else if (VALUEWITHOUTFUNC.length() == 6) {
return parseHex(VALUEWITHOUTFUNC) + 0xFF000000;
}
throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes) or 3 comma separated values");
} else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) {
return 1;
} else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) {
return 0;
}
if (VALUE.empty() || !isNumber(VALUE, false))
throw std::invalid_argument("cannot parse \"" + VALUE + "\" as an int.");
try {
const auto RES = std::stoll(VALUE);
return RES;
} catch (std::exception& e) { throw std::invalid_argument(std::string{"stoll threw: "} + e.what()); }
return 0;
}
int createPoolFile(size_t size, std::string& name) {
const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
if (!XDGRUNTIMEDIR) {
Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
return -1;
}
name = std::string(XDGRUNTIMEDIR) + "/.hyprlock_sc_XXXXXX";
const auto FD = mkstemp((char*)name.c_str());
if (FD < 0) {
Debug::log(CRIT, "createPoolFile: fd < 0");
return -1;
}
// set cloexec
long flags = fcntl(FD, F_GETFD);
if (flags == -1) {
close(FD);
return -1;
}
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
close(FD);
Debug::log(CRIT, "createPoolFile: fcntl < 0");
return -1;
}
if (ftruncate(FD, size) < 0) {
close(FD);
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
return -1;
}
return FD;
}
std::string spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}
if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
return proc.stdOut();
}
void spawnAsync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runAsync())
Debug::log(ERR, "Failed to start \"{}\"", cmd);
}
0707010000003E000081A4000000000000000000000001688B5FE900000176000000000000000000000000000000000000002D00000000hyprlock-0.9.1/src/helpers/MiscFunctions.hpp#pragma once
#include <string>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
std::string absolutePath(const std::string&, const std::string&);
int64_t configStringToInt(const std::string& VALUE);
int createPoolFile(size_t size, std::string& name);
std::string spawnSync(const std::string& cmd);
void spawnAsync(const std::string& cmd);
0707010000003F000081A4000000000000000000000001688B5FE900001307000000000000000000000000000000000000001C00000000hyprlock-0.9.1/src/main.cpp
#include "config/ConfigManager.hpp"
#include "core/hyprlock.hpp"
#include "helpers/Log.hpp"
#include "core/AnimationManager.hpp"
#include <cstddef>
#include <string_view>
void help() {
std::println("Usage: hyprlock [options]\n\n"
"Options:\n"
" -v, --verbose - Enable verbose logging\n"
" -q, --quiet - Disable logging\n"
" -c FILE, --config FILE - Specify config file to use\n"
" --display NAME - Specify the Wayland display to connect to\n"
" --grace SECONDS - Set grace period in seconds before requiring authentication\n"
" --immediate-render - Do not wait for resources before drawing the background\n"
" --no-fade-in - Disable the fade-in animation when the lock screen appears\n"
" -V, --version - Show version information\n"
" -h, --help - Show this help message");
}
std::optional<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::println(stderr, "Error: Missing value for {} option.", flag);
return std::nullopt;
}
}
static void printVersion() {
constexpr bool ISTAGGEDRELEASE = std::string_view(HYPRLOCK_COMMIT) == HYPRLOCK_VERSION_COMMIT;
if (ISTAGGEDRELEASE)
std::println("Hyprlock version v{}", HYPRLOCK_VERSION);
else
std::println("Hyprlock version v{} (commit {})", HYPRLOCK_VERSION, HYPRLOCK_COMMIT);
}
int main(int argc, char** argv, char** envp) {
std::string configPath;
std::string wlDisplay;
bool immediateRender = false;
bool noFadeIn = false;
int graceSeconds = 0;
std::vector<std::string> args(argv, argv + argc);
for (std::size_t i = 1; i < args.size(); ++i) {
const std::string arg = argv[i];
if (arg == "--help" || arg == "-h") {
help();
return 0;
}
if (arg == "--version" || arg == "-V") {
printVersion();
return 0;
}
if (arg == "--verbose" || arg == "-v")
Debug::verbose = true;
else if (arg == "--quiet" || arg == "-q")
Debug::quiet = true;
else if ((arg == "--config" || arg == "-c") && i + 1 < (std::size_t)argc) {
if (auto value = parseArg(args, arg, i); value)
configPath = *value;
else
return 1;
} else if (arg == "--display" && i + 1 < (std::size_t)argc) {
if (auto value = parseArg(args, arg, i); value)
wlDisplay = *value;
else
return 1;
} else if (arg == "--grace" && i + 1 < (std::size_t)argc) {
if (auto value = parseArg(args, arg, i); value) {
try {
graceSeconds = std::stoi(*value);
if (graceSeconds < 0) {
std::println(stderr, "Error: Grace period must be non-negative.");
return 1;
}
} catch (const std::exception&) {
std::println(stderr, "Error: Invalid grace period value: {}", *value);
return 1;
}
} else
return 1;
} else if (arg == "--immediate") {
graceSeconds = 0;
Debug::log(WARN, R"("--immediate" is deprecated. Use the "--grace" option instead.)");
}
else if (arg == "--immediate-render")
immediateRender = true;
else if (arg == "--no-fade-in")
noFadeIn = true;
else {
std::println(stderr, "Unknown option: {}", arg);
help();
return 1;
}
}
printVersion();
g_pAnimationManager = makeUnique<CHyprlockAnimationManager>();
try {
g_pConfigManager = makeUnique<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;
}
if (noFadeIn)
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediateRender, graceSeconds);
g_pHyprlock->run();
} catch (const std::exception& ex) {
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());
return 1;
}
return 0;
}
07070100000040000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000001C00000000hyprlock-0.9.1/src/renderer07070100000041000081A4000000000000000000000001688B5FE9000033ED000000000000000000000000000000000000003600000000hyprlock-0.9.1/src/renderer/AsyncResourceGatherer.cpp#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include <algorithm>
#include <cairo/cairo.h>
#include <filesystem>
#include <pango/pangocairo.h>
#include <sys/eventfd.h>
#include <hyprgraphics/image/Image.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
using namespace Hyprgraphics;
using namespace Hyprutils::OS;
CAsyncResourceGatherer::CAsyncResourceGatherer() {
if (g_pHyprlock->getScreencopy())
enqueueScreencopyFrames();
initialGatherThread = std::thread([this]() { this->gather(); });
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)};
if (!gatheredEventfd.isValid())
Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno));
}
void CAsyncResourceGatherer::enqueueScreencopyFrames() {
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn");
const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut");
const bool FADENEEDSSC = *ANIMATIONSENABLED &&
((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled
(FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled));
const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { //
return w.type == "background" && std::string{std::any_cast<Hyprlang::STRING>(w.values.at("path"))} == "screenshot";
});
if (!BGSCREENSHOT && !FADENEEDSSC) {
Debug::log(LOG, "Skipping screencopy");
return;
}
for (const auto& MON : g_pHyprlock->m_vOutputs) {
scframes.emplace_back(makeUnique<CScreencopyFrame>(MON));
}
}
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
if (id.contains(CScreencopyFrame::RESOURCEIDPREFIX)) {
for (auto& frame : scframes) {
if (id == frame->m_resourceID)
return frame->m_asset.ready ? &frame->m_asset : nullptr;
}
return nullptr;
}
for (auto& a : assets) {
if (a.first == id)
return &a.second;
}
if (apply()) {
for (auto& a : assets) {
if (a.first == id)
return &a.second;
}
};
return nullptr;
}
static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) {
auto image = CImage(path);
if (!image.success()) {
Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError());
return nullptr;
}
return image.cairoSurface();
}
void CAsyncResourceGatherer::gather() {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
g_pEGL->makeCurrent(nullptr);
// gather resources to preload
// clang-format off
int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) {
return w.type == "background" || w.type == "image";
});
// clang-format on
progress = 0;
for (auto& c : CWIDGETS) {
if (c.type == "background" || c.type == "image") {
#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100
progress = progress + 1.0 / (preloads + 1.0);
#else
progress += 1.0 / (preloads + 1.0);
#endif
std::string path = std::any_cast<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 (!g_pHyprlock->m_bTerminate && std::ranges::any_of(scframes, [](const auto& d) { return !d->m_asset.ready; })) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
gathered = true;
// wake hyprlock from poll
if (gatheredEventfd.isValid())
eventfd_write(gatheredEventfd.get(), 1);
}
bool CAsyncResourceGatherer::apply() {
preloadTargetsMutex.lock();
if (preloadTargets.empty()) {
preloadTargetsMutex.unlock();
return false;
}
auto currentPreloadTargets = preloadTargets;
preloadTargets.clear();
preloadTargetsMutex.unlock();
for (auto& t : currentPreloadTargets) {
if (t.type == TARGET_IMAGE) {
const auto ASSET = &assets[t.id];
const cairo_status_t SURFACESTATUS = (cairo_status_t)t.cairosurface->status();
const auto CAIROFORMAT = cairo_image_surface_get_format(t.cairosurface->cairo());
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS));
ASSET->texture.m_iType = TEXTURE_INVALID;
}
ASSET->texture.m_vSize = t.size;
ASSET->texture.allocate();
glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
cairo_destroy((cairo_t*)t.cairo);
t.cairosurface.reset();
} else
Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type);
}
return true;
}
void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
SPreloadTarget target;
target.type = TARGET_IMAGE;
target.id = rq.id;
std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, ""));
const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH);
if (!CAIROISURFACE) {
Debug::log(ERR, "renderImage: No cairo surface!");
return;
}
const auto CAIRO = cairo_create(CAIROISURFACE->cairo());
cairo_scale(CAIRO, 1, 1);
target.cairo = CAIRO;
target.cairosurface = CAIROISURFACE;
target.data = CAIROISURFACE->data();
target.size = CAIROISURFACE->size();
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
}
void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
SPreloadTarget target;
target.type = TARGET_IMAGE; /* text is just an image lol */
target.id = rq.id;
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0);
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<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 const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;
if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));
text.erase(text.find_last_not_of(" \n\r\t") + 1);
}
auto CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */));
auto CAIRO = cairo_create(CAIROSURFACE->cairo());
// draw title using Pango
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str());
pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE);
pango_layout_set_font_description(layout, fontDesc);
pango_font_description_free(fontDesc);
if (rq.props.contains("text_align")) {
const std::string TEXTALIGN = std::any_cast<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);
CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE));
CAIRO = cairo_create(CAIROSURFACE->cairo());
// clear the pixmap
cairo_save(CAIRO);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
// render the thing
cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a);
cairo_move_to(CAIRO, 0, 0);
pango_cairo_show_layout(CAIRO, layout);
g_object_unref(layout);
cairo_surface_flush(CAIROSURFACE->cairo());
target.cairo = CAIRO;
target.cairosurface = CAIROSURFACE;
target.data = CAIROSURFACE->data();
target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE};
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
}
void CAsyncResourceGatherer::asyncAssetSpinLock() {
while (!g_pHyprlock->m_bTerminate) {
std::unique_lock lk(asyncLoopState.requestsMutex);
if (!asyncLoopState.pending) // avoid a lock if a thread managed to request something already since we .unlock()ed
asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events
asyncLoopState.pending = false;
if (asyncLoopState.requests.empty()) {
lk.unlock();
continue;
}
auto requests = asyncLoopState.requests;
asyncLoopState.requests.clear();
lk.unlock();
// process requests
for (auto& r : requests) {
Debug::log(TRACE, "Processing requested resourceID {}", r.id);
if (r.type == TARGET_TEXT) {
renderText(r);
} else if (r.type == TARGET_IMAGE) {
renderImage(r);
} else {
Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type);
continue;
}
// plant timer for callback
if (r.callback)
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr);
}
}
}
void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) {
Debug::log(TRACE, "Requesting label resource {}", request.id);
std::lock_guard<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();
}
07070100000042000081A4000000000000000000000001688B5FE900000A5B000000000000000000000000000000000000003600000000hyprlock-0.9.1/src/renderer/AsyncResourceGatherer.hpp#pragma once
#include "Screencopy.hpp"
#include <thread>
#include <atomic>
#include <vector>
#include <unordered_map>
#include <condition_variable>
#include <any>
#include "Shared.hpp"
#include <hyprgraphics/cairo/CairoSurface.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
class CAsyncResourceGatherer {
public:
CAsyncResourceGatherer();
std::atomic<bool> gathered = false;
Hyprutils::OS::CFileDescriptor gatheredEventfd;
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.
std::function<void()> callback = 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 = nullptr;
void* cairo = nullptr;
SP<Hyprgraphics::CCairoSurface> cairosurface;
Vector2D size;
};
std::vector<UP<CScreencopyFrame>> scframes;
std::vector<SPreloadTarget> preloadTargets;
std::mutex preloadTargetsMutex;
std::unordered_map<std::string, SPreloadedAsset> assets;
void gather();
void enqueueScreencopyFrames();
};
07070100000043000081A4000000000000000000000001688B5FE900001066000000000000000000000000000000000000002C00000000hyprlock-0.9.1/src/renderer/Framebuffer.cpp#include "Framebuffer.hpp"
#include "../helpers/Log.hpp"
#include <hyprutils/os/FileDescriptor.hpp>
#include <libdrm/drm_fourcc.h>
#include <utility>
static uint32_t drmFormatToGL(uint32_t drm) {
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case.
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2;
default: return GL_RGBA;
}
return GL_RGBA;
}
static uint32_t glFormatToType(uint32_t gl) {
return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE;
}
bool CFramebuffer::alloc(int w, int h, bool highres) {
bool firstAlloc = false;
uint32_t glFormat = highres ? GL_RGBA16F : drmFormatToGL(DRM_FORMAT_XRGB2101010); // TODO: revise only 10b when I find a way to figure out without sc whether display is 10b
uint32_t glType = highres ? GL_FLOAT : glFormatToType(glFormat);
if (m_iFb == (uint32_t)-1) {
firstAlloc = true;
glGenFramebuffers(1, &m_iFb);
}
if (m_cTex.m_iTexID == 0) {
firstAlloc = true;
glGenTextures(1, &m_cTex.m_iTexID);
glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_cTex.m_vSize = {w, h};
}
if (firstAlloc || m_vSize != Vector2D(w, h)) {
glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0);
if (m_pStencilTex) {
glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0);
}
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
Debug::log(ERR, "Framebuffer incomplete, couldn't create! (FB status: {})", status);
abort();
}
Debug::log(TRACE, "Framebuffer created, status {}", status);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
m_vSize = Vector2D(w, h);
return true;
}
void CFramebuffer::addStencil() {
if (!m_pStencilTex) {
Debug::log(ERR, "No stencil texture allocated.");
return;
}
glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0);
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo! (FB status: {})", status);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void CFramebuffer::bind() const {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iFb);
glViewport(0, 0, m_vSize.x, m_vSize.y);
}
void CFramebuffer::destroyBuffer() {
if (m_iFb != (uint32_t)-1 && m_iFb)
glDeleteFramebuffers(1, &m_iFb);
if (m_cTex.m_iTexID)
glDeleteTextures(1, &m_cTex.m_iTexID);
if (m_pStencilTex && m_pStencilTex->m_iTexID)
glDeleteTextures(1, &m_pStencilTex->m_iTexID);
m_cTex.m_iTexID = 0;
m_iFb = -1;
m_vSize = Vector2D();
m_pStencilTex = nullptr;
}
CFramebuffer::~CFramebuffer() {
destroyBuffer();
}
bool CFramebuffer::isAllocated() const {
return m_iFb != (GLuint)-1;
}
07070100000044000081A4000000000000000000000001688B5FE900000254000000000000000000000000000000000000002C00000000hyprlock-0.9.1/src/renderer/Framebuffer.hpp#pragma once
#include "../helpers/Math.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 destroyBuffer();
bool isAllocated() const;
Vector2D m_vSize;
CTexture m_cTex;
GLuint m_iFb = -1;
CTexture* m_pStencilTex = nullptr;
CFramebuffer& operator=(CFramebuffer&&) = delete;
CFramebuffer& operator=(const CFramebuffer&) = delete;
};
07070100000045000081A4000000000000000000000001688B5FE900006641000000000000000000000000000000000000002900000000hyprlock-0.9.1/src/renderer/Renderer.cpp#include "Renderer.hpp"
#include "Shaders.hpp"
#include "Screencopy.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/AnimationManager.hpp"
#include "../core/Egl.hpp"
#include "../core/Output.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <algorithm>
#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, &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);
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag);
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag);
auto prog = glCreateProgram();
glAttachShader(prog, vertCompiled);
glAttachShader(prog, fragCompiled);
glLinkProgram(prog);
glDetachShader(prog, vertCompiled);
glDetachShader(prog, fragCompiled);
glDeleteShader(vertCompiled);
glDeleteShader(fragCompiled);
GLint ok;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!");
return prog;
}
static void glMessageCallbackA(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
if (type != GL_DEBUG_TYPE_ERROR)
return;
Debug::log(LOG, "[gl] {}", (const char*)message);
}
CRenderer::CRenderer() {
g_pEGL->makeCurrent(nullptr);
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(glMessageCallbackA, nullptr);
GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC);
rectShader.program = prog;
rectShader.proj = glGetUniformLocation(prog, "proj");
rectShader.color = glGetUniformLocation(prog, "color");
rectShader.posAttrib = glGetAttribLocation(prog, "pos");
rectShader.topLeft = glGetUniformLocation(prog, "topLeft");
rectShader.fullSize = glGetUniformLocation(prog, "fullSize");
rectShader.radius = glGetUniformLocation(prog, "radius");
prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA);
texShader.program = prog;
texShader.proj = glGetUniformLocation(prog, "proj");
texShader.tex = glGetUniformLocation(prog, "tex");
texShader.alphaMatte = glGetUniformLocation(prog, "texMatte");
texShader.alpha = glGetUniformLocation(prog, "alpha");
texShader.texAttrib = glGetAttribLocation(prog, "texcoord");
texShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
texShader.posAttrib = glGetAttribLocation(prog, "pos");
texShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
texShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
texShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
texShader.topLeft = glGetUniformLocation(prog, "topLeft");
texShader.fullSize = glGetUniformLocation(prog, "fullSize");
texShader.radius = glGetUniformLocation(prog, "radius");
texShader.applyTint = glGetUniformLocation(prog, "applyTint");
texShader.tint = glGetUniformLocation(prog, "tint");
texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
prog = createProgram(TEXVERTSRC, TEXMIXFRAGSRCRGBA);
texMixShader.program = prog;
texMixShader.proj = glGetUniformLocation(prog, "proj");
texMixShader.tex = glGetUniformLocation(prog, "tex1");
texMixShader.tex2 = glGetUniformLocation(prog, "tex2");
texMixShader.alphaMatte = glGetUniformLocation(prog, "texMatte");
texMixShader.alpha = glGetUniformLocation(prog, "alpha");
texMixShader.mixFactor = glGetUniformLocation(prog, "mixFactor");
texMixShader.texAttrib = glGetAttribLocation(prog, "texcoord");
texMixShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
texMixShader.posAttrib = glGetAttribLocation(prog, "pos");
texMixShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
texMixShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
texMixShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
texMixShader.topLeft = glGetUniformLocation(prog, "topLeft");
texMixShader.fullSize = glGetUniformLocation(prog, "fullSize");
texMixShader.radius = glGetUniformLocation(prog, "radius");
texMixShader.applyTint = glGetUniformLocation(prog, "applyTint");
texMixShader.tint = glGetUniformLocation(prog, "tint");
texMixShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
prog = createProgram(TEXVERTSRC, FRAGBLUR1);
blurShader1.program = prog;
blurShader1.tex = glGetUniformLocation(prog, "tex");
blurShader1.alpha = glGetUniformLocation(prog, "alpha");
blurShader1.proj = glGetUniformLocation(prog, "proj");
blurShader1.posAttrib = glGetAttribLocation(prog, "pos");
blurShader1.texAttrib = glGetAttribLocation(prog, "texcoord");
blurShader1.radius = glGetUniformLocation(prog, "radius");
blurShader1.halfpixel = glGetUniformLocation(prog, "halfpixel");
blurShader1.passes = glGetUniformLocation(prog, "passes");
blurShader1.vibrancy = glGetUniformLocation(prog, "vibrancy");
blurShader1.vibrancy_darkness = glGetUniformLocation(prog, "vibrancy_darkness");
prog = createProgram(TEXVERTSRC, FRAGBLUR2);
blurShader2.program = prog;
blurShader2.tex = glGetUniformLocation(prog, "tex");
blurShader2.alpha = glGetUniformLocation(prog, "alpha");
blurShader2.proj = glGetUniformLocation(prog, "proj");
blurShader2.posAttrib = glGetAttribLocation(prog, "pos");
blurShader2.texAttrib = glGetAttribLocation(prog, "texcoord");
blurShader2.radius = glGetUniformLocation(prog, "radius");
blurShader2.halfpixel = glGetUniformLocation(prog, "halfpixel");
prog = createProgram(TEXVERTSRC, FRAGBLURPREPARE);
blurPrepareShader.program = prog;
blurPrepareShader.tex = glGetUniformLocation(prog, "tex");
blurPrepareShader.proj = glGetUniformLocation(prog, "proj");
blurPrepareShader.posAttrib = glGetAttribLocation(prog, "pos");
blurPrepareShader.texAttrib = glGetAttribLocation(prog, "texcoord");
blurPrepareShader.contrast = glGetUniformLocation(prog, "contrast");
blurPrepareShader.brightness = glGetUniformLocation(prog, "brightness");
prog = createProgram(TEXVERTSRC, FRAGBLURFINISH);
blurFinishShader.program = prog;
blurFinishShader.tex = glGetUniformLocation(prog, "tex");
blurFinishShader.proj = glGetUniformLocation(prog, "proj");
blurFinishShader.posAttrib = glGetAttribLocation(prog, "pos");
blurFinishShader.texAttrib = glGetAttribLocation(prog, "texcoord");
blurFinishShader.brightness = glGetUniformLocation(prog, "brightness");
blurFinishShader.noise = glGetUniformLocation(prog, "noise");
blurFinishShader.colorize = glGetUniformLocation(prog, "colorize");
blurFinishShader.colorizeTint = glGetUniformLocation(prog, "colorizeTint");
blurFinishShader.boostA = glGetUniformLocation(prog, "boostA");
prog = createProgram(QUADVERTSRC, FRAGBORDER);
borderShader.program = prog;
borderShader.proj = glGetUniformLocation(prog, "proj");
borderShader.thick = glGetUniformLocation(prog, "thick");
borderShader.posAttrib = glGetAttribLocation(prog, "pos");
borderShader.texAttrib = glGetAttribLocation(prog, "texcoord");
borderShader.topLeft = glGetUniformLocation(prog, "topLeft");
borderShader.bottomRight = glGetUniformLocation(prog, "bottomRight");
borderShader.fullSize = glGetUniformLocation(prog, "fullSize");
borderShader.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed");
borderShader.radius = glGetUniformLocation(prog, "radius");
borderShader.radiusOuter = glGetUniformLocation(prog, "radiusOuter");
borderShader.gradient = glGetUniformLocation(prog, "gradient");
borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength");
borderShader.angle = glGetUniformLocation(prog, "angle");
borderShader.gradient2 = glGetUniformLocation(prog, "gradient2");
borderShader.gradient2Length = glGetUniformLocation(prog, "gradient2Length");
borderShader.angle2 = glGetUniformLocation(prog, "angle2");
borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp");
borderShader.alpha = glGetUniformLocation(prog, "alpha");
asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>();
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
}
//
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
g_pEGL->makeCurrent(surf.eglSurface);
glViewport(0, 0, surf.size.x, surf.size.y);
GLint fb = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb);
pushFb(fb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SRenderFeedback feedback;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
if (!WAITFORASSETS) {
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(surf);
for (auto& w : WIDGETS) {
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
}
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered;
glDisable(GL_BLEND);
return feedback;
}
void CRenderer::renderRect(const CBox& box, const CHyprColor& col, int rounding) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
glUseProgram(rectShader.program);
glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
// premultiply the color as well as we don't work with straight alpha
glUniform4f(rectShader.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(rectShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(rectShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform1f(rectShader.radius, rounding);
glVertexAttribPointer(rectShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(rectShader.posAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(rectShader.posAttrib);
}
void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding, float alpha) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
glUseProgram(borderShader.program);
glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4fv(borderShader.gradient, gradient.m_vColorsOkLabA.size() / 4, (float*)gradient.m_vColorsOkLabA.data());
glUniform1i(borderShader.gradientLength, gradient.m_vColorsOkLabA.size() / 4);
glUniform1f(borderShader.angle, (int)(gradient.m_fAngle / (M_PI / 180.0)) % 360 * (M_PI / 180.0));
glUniform1f(borderShader.alpha, alpha);
glUniform1i(borderShader.gradient2Length, 0);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
glUniform2f(borderShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(borderShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform2f(borderShader.fullSizeUntransformed, (float)box.width, (float)box.height);
glUniform1f(borderShader.radius, rounding);
glUniform1f(borderShader.radiusOuter, rounding);
glUniform1f(borderShader.thick, thickness);
glVertexAttribPointer(borderShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(borderShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(borderShader.posAttrib);
glEnableVertexAttribArray(borderShader.texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(borderShader.posAttrib);
glDisableVertexAttribArray(borderShader.texAttrib);
}
void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional<eTransform> tr) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CShader* shader = &texShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex.m_iTarget, tex.m_iTexID);
glUseProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1i(shader->tex, 0);
glUniform1f(shader->alpha, a);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
glUniform1f(shader->radius, rounding);
glUniform1i(shader->discardOpaque, 0);
glUniform1i(shader->discardAlpha, 0);
glUniform1i(shader->applyTint, 0);
glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(shader->posAttrib);
glEnableVertexAttribArray(shader->texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(shader->posAttrib);
glDisableVertexAttribArray(shader->texAttrib);
glBindTexture(tex.m_iTarget, 0);
}
void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a, float mixFactor, int rounding, std::optional<eTransform> tr) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CShader* shader = &texMixShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex.m_iTarget, tex.m_iTexID);
glActiveTexture(GL_TEXTURE1);
glBindTexture(tex2.m_iTarget, tex2.m_iTexID);
glUseProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1i(shader->tex, 0);
glUniform1i(shader->tex2, 1);
glUniform1f(shader->alpha, a);
glUniform1f(shader->mixFactor, mixFactor);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
glUniform1f(shader->radius, rounding);
glUniform1i(shader->discardOpaque, 0);
glUniform1i(shader->discardAlpha, 0);
glUniform1i(shader->applyTint, 0);
glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(shader->posAttrib);
glEnableVertexAttribArray(shader->texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(shader->posAttrib);
glDisableVertexAttribArray(shader->texAttrib);
glBindTexture(tex.m_iTarget, 0);
}
template <class Widget>
static void createWidget(std::vector<ASP<IWidget>>& widgets) {
const auto W = makeAtomicShared<Widget>();
W->registerSelf(W);
widgets.emplace_back(W);
}
std::vector<ASP<IWidget>>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface& surf) {
RASSERT(surf.m_outputID != OUTPUT_INVALID, "Invalid output ID!");
if (!widgets.contains(surf.m_outputID)) {
auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
std::ranges::sort(CWIDGETS, [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) {
return std::any_cast<Hyprlang::INT>(a.values.at("zindex")) < std::any_cast<Hyprlang::INT>(b.values.at("zindex"));
});
const auto POUTPUT = surf.m_outputRef.lock();
for (auto& c : CWIDGETS) {
if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !("desc:" + POUTPUT->stringDesc).starts_with(c.monitor))
continue;
// by type
if (c.type == "background") {
createWidget<CBackground>(widgets[surf.m_outputID]);
} else if (c.type == "input-field") {
createWidget<CPasswordInputField>(widgets[surf.m_outputID]);
} else if (c.type == "label") {
createWidget<CLabel>(widgets[surf.m_outputID]);
} else if (c.type == "shape") {
createWidget<CShape>(widgets[surf.m_outputID]);
} else if (c.type == "image") {
createWidget<CImage>(widgets[surf.m_outputID]);
} else {
Debug::log(ERR, "Unknown widget type: {}", c.type);
continue;
}
widgets[surf.m_outputID].back()->configure(c.values, POUTPUT);
}
}
return widgets[surf.m_outputID];
}
void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y};
box.round();
Mat3x3 matrix = projMatrix.projectBox(box, HYPRUTILS_TRANSFORM_NORMAL, 0);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CFramebuffer mirrors[2];
mirrors[0].alloc(outfb.m_vSize.x, outfb.m_vSize.y, true);
mirrors[1].alloc(outfb.m_vSize.x, outfb.m_vSize.y, true);
CFramebuffer* currentRenderToFB = &mirrors[0];
// Begin with base color adjustments - global brightness and contrast
// TODO: make this a part of the first pass maybe to save on a drawcall?
{
mirrors[1].bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(outfb.m_cTex.m_iTarget, outfb.m_cTex.m_iTexID);
glTexParameteri(outfb.m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glUseProgram(blurPrepareShader.program);
glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(blurPrepareShader.contrast, params.contrast);
glUniform1f(blurPrepareShader.brightness, params.brightness);
glUniform1i(blurPrepareShader.tex, 0);
glVertexAttribPointer(blurPrepareShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(blurPrepareShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(blurPrepareShader.posAttrib);
glEnableVertexAttribArray(blurPrepareShader.texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(blurPrepareShader.posAttrib);
glDisableVertexAttribArray(blurPrepareShader.texAttrib);
currentRenderToFB = &mirrors[1];
}
// declare the draw func
auto drawPass = [&](CShader* pShader) {
if (currentRenderToFB == &mirrors[0])
mirrors[1].bind();
else
mirrors[0].bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID);
glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glUseProgram(pShader->program);
// prep two shaders
glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(pShader->radius, params.size);
if (pShader == &blurShader1) {
glUniform2f(blurShader1.halfpixel, 0.5f / (outfb.m_vSize.x / 2.f), 0.5f / (outfb.m_vSize.y / 2.f));
glUniform1i(blurShader1.passes, params.passes);
glUniform1f(blurShader1.vibrancy, params.vibrancy);
glUniform1f(blurShader1.vibrancy_darkness, params.vibrancy_darkness);
} else
glUniform2f(blurShader2.halfpixel, 0.5f / (outfb.m_vSize.x * 2.f), 0.5f / (outfb.m_vSize.y * 2.f));
glUniform1i(pShader->tex, 0);
glVertexAttribPointer(pShader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(pShader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(pShader->posAttrib);
glEnableVertexAttribArray(pShader->texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(pShader->posAttrib);
glDisableVertexAttribArray(pShader->texAttrib);
if (currentRenderToFB != &mirrors[0])
currentRenderToFB = &mirrors[0];
else
currentRenderToFB = &mirrors[1];
};
// draw the things.
// first draw is swap -> mirr
mirrors[0].bind();
glBindTexture(mirrors[1].m_cTex.m_iTarget, mirrors[1].m_cTex.m_iTexID);
for (int i = 1; i <= params.passes; ++i) {
drawPass(&blurShader1); // down
}
for (int i = params.passes - 1; i >= 0; --i) {
drawPass(&blurShader2); // up
}
// finalize the image
{
if (currentRenderToFB == &mirrors[0])
mirrors[1].bind();
else
mirrors[0].bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID);
glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glUseProgram(blurFinishShader.program);
glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(blurFinishShader.noise, params.noise);
glUniform1f(blurFinishShader.brightness, params.brightness);
glUniform1i(blurFinishShader.colorize, params.colorize.has_value());
if (params.colorize.has_value())
glUniform3f(blurFinishShader.colorizeTint, params.colorize->r, params.colorize->g, params.colorize->b);
glUniform1f(blurFinishShader.boostA, params.boostA);
glUniform1i(blurFinishShader.tex, 0);
glVertexAttribPointer(blurFinishShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(blurFinishShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(blurFinishShader.posAttrib);
glEnableVertexAttribArray(blurFinishShader.texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(blurFinishShader.posAttrib);
glDisableVertexAttribArray(blurFinishShader.texAttrib);
if (currentRenderToFB != &mirrors[0])
currentRenderToFB = &mirrors[0];
else
currentRenderToFB = &mirrors[1];
}
// finish
outfb.bind();
renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, HYPRUTILS_TRANSFORM_NORMAL);
glEnable(GL_BLEND);
}
void CRenderer::pushFb(GLint fb) {
boundFBs.push_back(fb);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);
}
void CRenderer::popFb() {
boundFBs.pop_back();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundFBs.empty() ? 0 : boundFBs.back());
}
void CRenderer::removeWidgetsFor(OUTPUTID id) {
widgets.erase(id);
}
void CRenderer::reconfigureWidgetsFor(OUTPUTID id) {
// TODO: reconfigure widgets by just calling their configure method again.
// Requires a way to get a widgets config properties.
// I think the best way would be to store the anonymos key of the widget config.
removeWidgetsFor(id);
}
void CRenderer::startFadeIn() {
Debug::log(LOG, "Starting fade in");
*opacity = 1.f;
opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true);
}
void CRenderer::startFadeOut(bool unlock) {
*opacity = 0.f;
if (unlock)
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
}
07070100000046000081A4000000000000000000000001688B5FE900000A76000000000000000000000000000000000000002900000000hyprlock-0.9.1/src/renderer/Renderer.hpp#pragma once
#include <chrono>
#include <optional>
#include "Shader.hpp"
#include "../defines.hpp"
#include "../core/LockSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Color.hpp"
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigDataValues.hpp"
#include "widgets/IWidget.hpp"
#include "Framebuffer.hpp"
typedef std::unordered_map<OUTPUTID, std::vector<ASP<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<CHyprColor> colorize;
float boostA = 1.0;
};
SRenderFeedback renderLock(const CSessionLockSurface& surf);
void renderRect(const CBox& box, const CHyprColor& col, int rounding = 0);
void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0);
void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional<eTransform> tr = {});
void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional<eTransform> tr = {});
void blurFB(const CFramebuffer& outfb, SBlurParams params);
UP<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point firstFullFrameTime;
void pushFb(GLint fb);
void popFb();
void removeWidgetsFor(OUTPUTID id);
void reconfigureWidgetsFor(OUTPUTID id);
void startFadeIn();
void startFadeOut(bool unlock = false);
std::vector<ASP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
private:
widgetMap_t widgets;
CShader rectShader;
CShader texShader;
CShader texMixShader;
CShader blurShader1;
CShader blurShader2;
CShader blurPrepareShader;
CShader blurFinishShader;
CShader borderShader;
Mat3x3 projMatrix = Mat3x3::identity();
Mat3x3 projection;
PHLANIMVAR<float> opacity;
std::vector<GLint> boundFBs;
};
inline UP<CRenderer> g_pRenderer;
07070100000047000081A4000000000000000000000001688B5FE9000048F1000000000000000000000000000000000000002B00000000hyprlock-0.9.1/src/renderer/Screencopy.cpp#include "Screencopy.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../core/hyprlock.hpp"
#include "../core/Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <cstring>
#include <array>
#include <cstdint>
#include <gbm.h>
#include <hyprutils/memory/UniquePtr.hpp>
#include <unistd.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
//
std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) {
return RESOURCEIDPREFIX + std::format(":{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y);
}
CScreencopyFrame::CScreencopyFrame(SP<COutput> pOutput) : m_outputRef(pOutput) {
captureOutput();
static const auto SCMODE = g_pConfigManager->getValue<Hyprlang::INT>("general:screencopy_mode");
if (*SCMODE == 1)
m_frame = makeUnique<CSCSHMFrame>(m_sc);
else
m_frame = makeUnique<CSCDMAFrame>(m_sc);
}
void CScreencopyFrame::captureOutput() {
const auto POUTPUT = m_outputRef.lock();
RASSERT(POUTPUT, "Screencopy, but no valid output");
m_resourceID = getResourceId(POUTPUT);
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, POUTPUT->m_wlOutput->resource()));
m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) {
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this);
if (!m_frame || !m_frame->onBufferDone() || !m_frame->m_wlBuffer) {
Debug::log(ERR, "[sc] Failed to create a wayland buffer for the screencopy frame");
return;
}
m_sc->sendCopy(m_frame->m_wlBuffer->resource());
Debug::log(TRACE, "[sc] wlr frame copied");
});
m_sc->setFailed([this](CCZwlrScreencopyFrameV1* r) {
Debug::log(ERR, "[sc] wlrOnFailed for {}", (void*)r);
m_frame.reset();
});
m_sc->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) {
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this);
if (!m_frame || !m_frame->onBufferReady(m_asset)) {
Debug::log(ERR, "[sc] Failed to bind the screencopy buffer to a texture");
return;
}
m_sc.reset();
});
}
CSCDMAFrame::CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
if (!glEGLImageTargetTexture2DOES) {
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!glEGLImageTargetTexture2DOES) {
Debug::log(ERR, "No glEGLImageTargetTexture2DOES??");
return;
}
}
if (!eglQueryDmaBufModifiersEXT)
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
m_sc->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this);
m_w = width;
m_h = height;
m_fmt = format;
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
});
m_sc->setBuffer([](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
; // unused by dma
});
}
CSCDMAFrame::~CSCDMAFrame() {
if (g_pEGL)
eglDestroyImage(g_pEGL->eglDisplay, m_image);
// leaks bo and stuff but lives throughout so for now who cares
}
bool CSCDMAFrame::onBufferDone() {
uint32_t flags = GBM_BO_USE_RENDERING;
if (!eglQueryDmaBufModifiersEXT) {
Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support");
m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags);
} else {
std::array<uint64_t, 64> mods;
std::array<EGLBoolean, 64> externalOnly;
int num = 0;
if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, m_fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) {
Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo");
m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags);
} else {
Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num);
std::vector<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.emplace_back(mods[i]);
}
m_bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, goodMods.data(), goodMods.size(), flags);
}
}
if (!m_bo) {
Debug::log(ERR, "[bo] Couldn't create a drm buffer");
return false;
}
m_planes = gbm_bo_get_plane_count(m_bo);
Debug::log(LOG, "[bo] has {} plane(s)", m_planes);
m_mod = gbm_bo_get_modifier(m_bo);
Debug::log(LOG, "[bo] chose modifier {:x}", m_mod);
auto params = makeShared<CCZwpLinuxBufferParamsV1>(g_pHyprlock->dma.linuxDmabuf->sendCreateParams());
if (!params) {
Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed");
gbm_bo_destroy(m_bo);
return false;
}
for (size_t plane = 0; plane < (size_t)m_planes; plane++) {
m_stride[plane] = gbm_bo_get_stride_for_plane(m_bo, plane);
m_offset[plane] = gbm_bo_get_offset(m_bo, plane);
m_fd[plane] = gbm_bo_get_fd_for_plane(m_bo, plane);
if (m_fd[plane] < 0) {
Debug::log(ERR, "gbm_m_bo_get_fd_for_plane failed");
params.reset();
gbm_bo_destroy(m_bo);
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
close(m_fd[plane_tmp]);
}
return false;
}
params->sendAdd(m_fd[plane], plane, m_offset[plane], m_stride[plane], m_mod >> 32, m_mod & 0xffffffff);
}
m_wlBuffer = makeShared<CCWlBuffer>(params->sendCreateImmed(m_w, m_h, m_fmt, (zwpLinuxBufferParamsV1Flags)0));
params.reset();
if (!m_wlBuffer) {
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
gbm_bo_destroy(m_bo);
for (size_t plane = 0; plane < (size_t)m_planes; plane++)
close(m_fd[plane]);
return false;
}
return true;
}
bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) {
static constexpr struct {
EGLAttrib fd;
EGLAttrib offset;
EGLAttrib pitch;
EGLAttrib modlo;
EGLAttrib modhi;
} attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT,
.offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE1_FD_EXT,
.offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE2_FD_EXT,
.offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE3_FD_EXT,
.offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}};
std::vector<EGLAttrib> attribs = {
EGL_WIDTH, m_w, EGL_HEIGHT, m_h, EGL_LINUX_DRM_FOURCC_EXT, m_fmt,
};
for (int i = 0; i < m_planes; i++) {
attribs.emplace_back(attrNames[i].fd);
attribs.emplace_back(m_fd[i]);
attribs.emplace_back(attrNames[i].offset);
attribs.emplace_back(m_offset[i]);
attribs.emplace_back(attrNames[i].pitch);
attribs.emplace_back(m_stride[i]);
if (m_mod != DRM_FORMAT_MOD_INVALID) {
attribs.emplace_back(attrNames[i].modlo);
attribs.emplace_back(m_mod & 0xFFFFFFFF);
attribs.emplace_back(attrNames[i].modhi);
attribs.emplace_back(m_mod >> 32);
}
}
attribs.emplace_back(EGL_NONE);
m_image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
if (m_image == EGL_NO_IMAGE) {
Debug::log(ERR, "Failed creating an egl image");
return false;
}
asset.texture.allocate();
asset.texture.m_vSize = {m_w, m_h};
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "Got dma frame with size {}", asset.texture.m_vSize);
asset.ready = true;
return true;
}
CSCSHMFrame::CSCSHMFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
Debug::log(TRACE, "[sc] [shm] Creating a SHM frame");
m_sc->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
Debug::log(TRACE, "[sc] [shm] wlrOnBuffer for {}", (void*)this);
const auto SIZE = stride * height;
m_shmFmt = format;
m_w = width;
m_h = height;
m_stride = stride;
// Create a shm pool with format and size
std::string shmPoolFile;
const auto FD = createPoolFile(SIZE, shmPoolFile);
if (FD < 0) {
Debug::log(ERR, "[sc] [shm] failed to create a pool file");
return;
}
m_shmData = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
if (m_shmData == MAP_FAILED) {
Debug::log(ERR, "[sc] [shm] failed to (errno {})", strerror(errno));
close(FD);
m_ok = false;
return;
}
if (!g_pHyprlock->getShm()) {
Debug::log(ERR, "[sc] [shm] Failed to get WLShm global");
close(FD);
m_ok = false;
return;
}
auto pShmPool = makeShared<CCWlShmPool>(g_pHyprlock->getShm()->sendCreatePool(FD, SIZE));
m_wlBuffer = makeShared<CCWlBuffer>(pShmPool->sendCreateBuffer(0, width, height, stride, m_shmFmt));
pShmPool.reset();
close(FD);
});
m_sc->setLinuxDmabuf([](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) {
; // unused by scshm
});
}
CSCSHMFrame::~CSCSHMFrame() {
if (m_convBuffer)
free(m_convBuffer);
if (m_shmData)
munmap(m_shmData, m_stride * m_h);
}
void CSCSHMFrame::convertBuffer() {
const auto BYTESPERPX = m_stride / m_w;
if (BYTESPERPX == 4) {
switch (m_shmFmt) {
case WL_SHM_FORMAT_ARGB8888:
case WL_SHM_FORMAT_XRGB8888: {
Debug::log(LOG, "[sc] [shm] Converting ARGB to RGBA");
uint8_t* data = (uint8_t*)m_shmData;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4));
// RGBA
*px = {.blue = px->red, .green = px->green, .red = px->blue, .alpha = px->alpha};
}
}
} break;
case WL_SHM_FORMAT_ABGR8888:
case WL_SHM_FORMAT_XBGR8888: {
Debug::log(LOG, "[sc] [shm] Converting ABGR to RGBA");
uint8_t* data = (uint8_t*)m_shmData;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4));
// BGRA
*px = {.blue = px->blue, .green = px->green, .red = px->red, .alpha = px->alpha};
}
}
} break;
case WL_SHM_FORMAT_ABGR2101010:
case WL_SHM_FORMAT_ARGB2101010:
case WL_SHM_FORMAT_XRGB2101010:
case WL_SHM_FORMAT_XBGR2101010: {
Debug::log(LOG, "[sc] [shm] Converting 10-bit channels to 8-bit");
uint8_t* data = (uint8_t*)m_shmData;
const bool FLIP = m_shmFmt != WL_SHM_FORMAT_XBGR2101010;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
uint32_t* px = (uint32_t*)(data + (y * m_w * 4) + (x * 4));
// conv to 8 bit
uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0));
uint8_t G = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000011111111110000000000) >> 10) / 1023.0));
uint8_t B = (uint8_t)std::round((255.0 * (((*px) & 0b00111111111100000000000000000000) >> 20) / 1023.0));
uint8_t A = (uint8_t)std::round((255.0 * (((*px) & 0b11000000000000000000000000000000) >> 30) / 3.0));
// write 8-bit values
*px = ((FLIP ? B : R) << 0) + (G << 8) + ((FLIP ? R : B) << 16) + (A << 24);
}
}
} break;
default: {
Debug::log(WARN, "[sc] [shm] Unsupported format {}", m_shmFmt);
}
}
} else if (BYTESPERPX == 3) {
Debug::log(LOG, "[sc] [shm] Converting 24 bit to 32 bit");
m_convBuffer = malloc(m_w * m_h * 4);
const int NEWSTRIDE = m_w * 4;
RASSERT(m_convBuffer, "malloc failed");
switch (m_shmFmt) {
case WL_SHM_FORMAT_BGR888: {
Debug::log(LOG, "[sc] [shm] Converting BGR to RGBA");
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel3 {
// little-endian RGB
unsigned char blue;
unsigned char green;
unsigned char red;
}* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3));
struct pixel4 {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4));
*dstPx = {.blue = srcPx->blue, .green = srcPx->green, .red = srcPx->red, .alpha = 0xFF};
}
}
} break;
case WL_SHM_FORMAT_RGB888: {
Debug::log(LOG, "[sc] [shm] Converting RGB to RGBA");
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel3 {
// big-endian RGB
unsigned char red;
unsigned char green;
unsigned char blue;
}* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3));
struct pixel4 {
// big-endian ARGB
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
}* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4));
*dstPx = {.alpha = srcPx->red, .red = srcPx->green, .green = srcPx->blue, .blue = 0xFF};
}
}
} break;
default: {
Debug::log(ERR, "[sc] [shm] Unsupported format for 24bit buffer {}", m_shmFmt);
}
}
} else {
Debug::log(ERR, "[sc] [shm] Unsupported bytes per pixel {}", BYTESPERPX);
}
}
bool CSCSHMFrame::onBufferReady(SPreloadedAsset& asset) {
convertBuffer();
asset.texture.allocate();
asset.texture.m_vSize.x = m_w;
asset.texture.m_vSize.y = m_h;
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
void* buffer = m_convBuffer ? m_convBuffer : m_shmData;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset.texture.m_vSize);
asset.ready = true;
return true;
}
07070100000048000081A4000000000000000000000001688B5FE9000009CE000000000000000000000000000000000000002B00000000hyprlock-0.9.1/src/renderer/Screencopy.hpp#pragma once
#include "../defines.hpp"
#include "../core/Output.hpp"
#include <cstdint>
#include <gbm.h>
#include <memory>
#include "Shared.hpp"
#include "linux-dmabuf-v1.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
class ISCFrame {
public:
ISCFrame() = default;
virtual ~ISCFrame() = default;
virtual bool onBufferDone() = 0;
virtual bool onBufferReady(SPreloadedAsset& asset) = 0;
SP<CCWlBuffer> m_wlBuffer = nullptr;
};
class CScreencopyFrame {
public:
static std::string getResourceId(SP<COutput> pOutput);
static constexpr const std::string RESOURCEIDPREFIX = "screencopy";
CScreencopyFrame(SP<COutput> pOutput);
~CScreencopyFrame() = default;
void captureOutput();
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
std::string m_resourceID;
SPreloadedAsset m_asset;
private:
WP<COutput> m_outputRef;
UP<ISCFrame> m_frame = nullptr;
bool m_dmaFailed = false;
};
// Uses a gpu buffer created via gbm_bo
class CSCDMAFrame : public ISCFrame {
public:
CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc);
virtual ~CSCDMAFrame();
virtual bool onBufferReady(SPreloadedAsset& asset);
virtual bool onBufferDone();
private:
gbm_bo* m_bo = nullptr;
int m_planes = 0;
uint64_t m_mod = 0;
int m_fd[4];
uint32_t m_stride[4], m_offset[4];
int m_w = 0, m_h = 0;
uint32_t m_fmt = 0;
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
EGLImage m_image = nullptr;
};
// Uses a shm buffer - is slow and needs ugly format conversion
// Used as a fallback just in case.
class CSCSHMFrame : public ISCFrame {
public:
CSCSHMFrame(SP<CCZwlrScreencopyFrameV1> sc);
virtual ~CSCSHMFrame();
virtual bool onBufferDone() {
return m_ok;
}
virtual bool onBufferReady(SPreloadedAsset& asset);
void convertBuffer();
private:
bool m_ok = true;
uint32_t m_w = 0, m_h = 0;
uint32_t m_stride = 0;
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
uint32_t m_shmFmt = 0;
void* m_shmData = nullptr;
void* m_convBuffer = nullptr;
};
07070100000049000081A4000000000000000000000001688B5FE9000001CB000000000000000000000000000000000000002700000000hyprlock-0.9.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;
}0707010000004A000081A4000000000000000000000001688B5FE900000785000000000000000000000000000000000000002700000000hyprlock-0.9.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 tex2 = -1;
GLint alpha = -1;
GLfloat mixFactor = -1;
GLint posAttrib = -1;
GLint texAttrib = -1;
GLint matteTexAttrib = -1;
GLint discardOpaque = -1;
GLint discardAlpha = -1;
GLfloat discardAlphaValue = -1;
GLint topLeft = -1;
GLint bottomRight = -1;
GLint fullSize = -1;
GLint fullSizeUntransformed = -1;
GLint radius = -1;
GLint radiusOuter = -1;
GLint thick = -1;
GLint halfpixel = -1;
GLint range = -1;
GLint shadowPower = -1;
GLint useAlphaMatte = -1; // always inverted
GLint applyTint = -1;
GLint tint = -1;
GLint gradient = -1;
GLint gradientLength = -1;
GLint gradient2 = -1;
GLint gradient2Length = -1;
GLint gradientLerp = -1;
GLint angle = -1;
GLint angle2 = -1;
GLint time = -1;
GLint distort = -1;
GLint wl_output = -1;
// Blur prepare
GLint contrast = -1;
// Blur
GLint passes = -1; // Used by `vibrancy`
GLint vibrancy = -1;
GLint vibrancy_darkness = -1;
// Blur finish
GLint brightness = -1;
GLint noise = -1;
// colorize
GLint colorize = -1;
GLint colorizeTint = -1;
GLint boostA = -1;
GLint getUniformLocation(const std::string&);
void destroy();
private:
std::unordered_map<std::string, GLint> m_muUniforms;
};0707010000004B000081A4000000000000000000000001688B5FE900004172000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/renderer/Shaders.hpp#pragma once
#include <string>
#include <format>
#include <cmath>
constexpr float SHADER_ROUNDED_SMOOTHING_FACTOR = M_PI / 5.34665792551;
inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVarName) -> std::string {
return R"#(
// branchless baby!
highp vec2 pixCoord = vec2(gl_FragCoord);
pixCoord -= topLeft + fullSize * 0.5;
pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;
pixCoord -= fullSize * 0.5 - radius;
pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left
// smoothing constant for the edge: more = blurrier, but smoother
const float SMOOTHING_CONSTANT = )#" +
std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(;
if (pixCoord.x + pixCoord.y > radius) {
float dist = length(pixCoord);
if (dist > radius + SMOOTHING_CONSTANT * 2.0)
discard;
if (dist > radius - SMOOTHING_CONSTANT * 2.0) {
float dist = length(pixCoord);
float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
)#" +
colorVarName + R"#( = )#" + colorVarName + R"#( * normalized;
}
}
)#";
};
inline const std::string QUADVERTSRC = R"#(
uniform mat3 proj;
uniform vec4 color;
attribute vec2 pos;
attribute vec2 texcoord;
attribute vec2 texcoordMatte;
varying vec4 v_color;
varying vec2 v_texcoord;
varying vec2 v_texcoordMatte;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_color = color;
v_texcoord = texcoord;
v_texcoordMatte = texcoordMatte;
})#";
inline const std::string QUADFRAGSRC = R"#(
precision highp float;
varying vec4 v_color;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
void main() {
vec4 pixColor = v_color;
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor;
})#";
inline const std::string TEXVERTSRC = R"#(
uniform mat3 proj;
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_texcoord = texcoord;
})#";
inline const std::string TEXFRAGSRCRGBA = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float alpha;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
uniform int discardOpaque;
uniform int discardAlpha;
uniform float discardAlphaValue;
uniform int applyTint;
uniform vec3 tint;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
if (discardOpaque == 1 && pixColor[3] * alpha == 1.0)
discard;
if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue)
discard;
if (applyTint == 1) {
pixColor[0] = pixColor[0] * tint[0];
pixColor[1] = pixColor[1] * tint[1];
pixColor[2] = pixColor[2] * tint[2];
}
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor * alpha;
})#";
inline const std::string TEXMIXFRAGSRCRGBA = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform float mixFactor;
uniform float alpha;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
uniform int discardOpaque;
uniform int discardAlpha;
uniform float discardAlphaValue;
uniform int applyTint;
uniform vec3 tint;
void main() {
vec4 pixColor = mix(texture2D(tex1, v_texcoord), texture2D(tex2, v_texcoord), smoothstep(0.0, 1.0, mixFactor));
if (discardOpaque == 1 && pixColor[3] * alpha == 1.0)
discard;
if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue)
discard;
if (applyTint == 1) {
pixColor[0] = pixColor[0] * tint[0];
pixColor[1] = pixColor[1] * tint[1];
pixColor[2] = pixColor[2] * tint[2];
}
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor * alpha;
})#";
inline const std::string FRAGBLUR1 = R"#(
#version 100
precision highp float;
varying highp vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float radius;
uniform vec2 halfpixel;
uniform int passes;
uniform float vibrancy;
uniform float vibrancy_darkness;
// see http://alienryderflex.com/hsp.html
const float Pr = 0.299;
const float Pg = 0.587;
const float Pb = 0.114;
// Y is "v" ( brightness ). X is "s" ( saturation )
// see https://www.desmos.com/3d/a88652b9a4
// Determines if high brightness or high saturation is more important
const float a = 0.93;
const float b = 0.11;
const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors
//
// http://www.flong.com/archive/texts/code/shapers_circ/
float doubleCircleSigmoid(float x, float a) {
a = clamp(a, 0.0, 1.0);
float y = .0;
if (x <= a) {
y = a - sqrt(a * a - x * x);
} else {
y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.));
}
return y;
}
vec3 rgb2hsl(vec3 col) {
float red = col.r;
float green = col.g;
float blue = col.b;
float minc = min(col.r, min(col.g, col.b));
float maxc = max(col.r, max(col.g, col.b));
float delta = maxc - minc;
float lum = (minc + maxc) * 0.5;
float sat = 0.0;
float hue = 0.0;
if (lum > 0.0 && lum < 1.0) {
float mul = (lum < 0.5) ? (lum) : (1.0 - lum);
sat = delta / (mul * 2.0);
}
if (delta > 0.0) {
vec3 maxcVec = vec3(maxc);
vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red)));
vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta;
hue += dot(adds, masks);
hue /= 6.0;
if (hue < 0.0)
hue += 1.0;
}
return vec3(hue, sat, lum);
}
vec3 hsl2rgb(vec3 col) {
const float onethird = 1.0 / 3.0;
const float twothird = 2.0 / 3.0;
const float rcpsixth = 6.0;
float hue = col.x;
float sat = col.y;
float lum = col.z;
vec3 xt = vec3(0.0);
if (hue < onethird) {
xt.r = rcpsixth * (onethird - hue);
xt.g = rcpsixth * hue;
xt.b = 0.0;
} else if (hue < twothird) {
xt.r = 0.0;
xt.g = rcpsixth * (twothird - hue);
xt.b = rcpsixth * (hue - onethird);
} else
xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue));
xt = min(xt, 1.0);
float sat2 = 2.0 * sat;
float satinv = 1.0 - sat;
float luminv = 1.0 - lum;
float lum2m1 = (2.0 * lum) - 1.0;
vec3 ct = (sat2 * xt) + satinv;
vec3 rgb;
if (lum >= 0.5)
rgb = (luminv * ct) + lum2m1;
else
rgb = lum * ct;
return rgb;
}
void main() {
vec2 uv = v_texcoord * 2.0;
vec4 sum = texture2D(tex, uv) * 4.0;
sum += texture2D(tex, uv - halfpixel.xy * radius);
sum += texture2D(tex, uv + halfpixel.xy * radius);
sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius);
sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius);
vec4 color = sum / 8.0;
if (vibrancy == 0.0) {
gl_FragColor = color;
} else {
// Invert it so that it correctly maps to the config setting
float vibrancy_darkness1 = 1.0 - vibrancy_darkness;
// Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest.
vec3 hsl = rgb2hsl(color.rgb);
// Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow
float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1);
float b1 = b * vibrancy_darkness1;
float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0;
float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0);
vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2]));
gl_FragColor = vec4(newColor, color[3]);
}
}
)#";
inline const std::string FRAGBLUR2 = R"#(
#version 100
precision highp float;
varying highp vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float radius;
uniform vec2 halfpixel;
void main() {
vec2 uv = v_texcoord / 2.0;
vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius);
sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0;
sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius);
sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0;
sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius);
sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0;
sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius);
sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0;
gl_FragColor = sum / 12.0;
}
)#";
inline const std::string FRAGBLURPREPARE = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float contrast;
uniform float brightness;
float gain(float x, float k) {
float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k);
return (x < 0.5) ? a : 1.0 - a;
}
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
// contrast
if (contrast != 1.0) {
pixColor.r = gain(pixColor.r, contrast);
pixColor.g = gain(pixColor.g, contrast);
pixColor.b = gain(pixColor.b, contrast);
}
// brightness
if (brightness > 1.0) {
pixColor.rgb *= brightness;
}
gl_FragColor = pixColor;
}
)#";
inline const std::string FRAGBLURFINISH = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float noise;
uniform float brightness;
uniform int colorize;
uniform vec3 colorizeTint;
uniform float boostA;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
// noise
float noiseHash = hash(v_texcoord);
float noiseAmount = (mod(noiseHash, 1.0) - 0.5);
pixColor.rgb += noiseAmount * noise;
// brightness
if (brightness < 1.0) {
pixColor.rgb *= brightness;
}
pixColor.a *= boostA;
if (colorize == 1) {
gl_FragColor = vec4(colorizeTint.r * pixColor.a, colorizeTint.g * pixColor.a, colorizeTint.b * pixColor.a, pixColor.a);
return;
}
gl_FragColor = pixColor;
}
)#";
// makes a stencil without corners
inline const std::string FRAGBORDER = R"#(
precision highp float;
varying vec4 v_color;
varying vec2 v_texcoord;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform vec2 fullSizeUntransformed;
uniform float radius;
uniform float radiusOuter;
uniform float thick;
// Gradients are in OkLabA!!!! {l, a, b, alpha}
uniform vec4 gradient[10];
uniform vec4 gradient2[10];
uniform int gradientLength;
uniform int gradient2Length;
uniform float angle;
uniform float angle2;
uniform float gradientLerp;
uniform float alpha;
float linearToGamma(float x) {
return x >= 0.0031308 ? 1.055 * pow(x, 0.416666666) - 0.055 : 12.92 * x;
}
vec4 okLabAToSrgb(vec4 lab) {
float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0);
float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0);
float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0);
return vec4(linearToGamma(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292),
linearToGamma(l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965)),
linearToGamma(l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010),
lab[3]);
}
vec4 getOkColorForCoordArray1(vec2 normalizedCoord) {
if (gradientLength < 2)
return gradient[0];
float finalAng = 0.0;
if (angle > 4.71 /* 270 deg */) {
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = 6.28 - angle;
} else if (angle > 3.14 /* 180 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = angle - 3.14;
} else if (angle > 1.57 /* 90 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
finalAng = 3.14 - angle;
} else {
finalAng = angle;
}
float sine = sin(finalAng);
float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1);
int bottom = int(floor(progress));
int top = bottom + 1;
return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress);
}
vec4 getOkColorForCoordArray2(vec2 normalizedCoord) {
if (gradient2Length < 2)
return gradient2[0];
float finalAng = 0.0;
if (angle2 > 4.71 /* 270 deg */) {
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = 6.28 - angle;
} else if (angle2 > 3.14 /* 180 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = angle - 3.14;
} else if (angle2 > 1.57 /* 90 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
finalAng = 3.14 - angle2;
} else {
finalAng = angle2;
}
float sine = sin(finalAng);
float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1);
int bottom = int(floor(progress));
int top = bottom + 1;
return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress);
}
vec4 getColorForCoord(vec2 normalizedCoord) {
vec4 result1 = getOkColorForCoordArray1(normalizedCoord);
if (gradient2Length <= 0)
return okLabAToSrgb(result1);
vec4 result2 = getOkColorForCoordArray2(normalizedCoord);
return okLabAToSrgb(mix(result1, result2, gradientLerp));
}
void main() {
highp vec2 pixCoord = vec2(gl_FragCoord);
highp vec2 pixCoordOuter = pixCoord;
highp vec2 originalPixCoord = v_texcoord;
originalPixCoord *= fullSizeUntransformed;
float additionalAlpha = 1.0;
vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0);
bool done = false;
pixCoord -= topLeft + fullSize * 0.5;
pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;
pixCoordOuter = pixCoord;
pixCoord -= fullSize * 0.5 - radius;
pixCoordOuter -= fullSize * 0.5 - radiusOuter;
// center the pixes dont make it top-left
pixCoord += vec2(1.0, 1.0) / fullSize;
pixCoordOuter += vec2(1.0, 1.0) / fullSize;
if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) {
// smoothing constant for the edge: more = blurrier, but smoother
const float SMOOTHING_CONSTANT = )#" +
std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(;
float dist = length(pixCoord);
float distOuter = length(pixCoordOuter);
float h = (thick / 2.0);
if (dist < radius - h) {
// lower
float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
additionalAlpha *= normalized;
done = true;
} else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) {
// higher
float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
additionalAlpha *= normalized;
done = true;
} else if (distOuter < radiusOuter - h) {
additionalAlpha = 1.0;
done = true;
}
}
// now check for other shit
if (!done) {
// distance to all straight bb borders
float distanceT = originalPixCoord[1];
float distanceB = fullSizeUntransformed[1] - originalPixCoord[1];
float distanceL = originalPixCoord[0];
float distanceR = fullSizeUntransformed[0] - originalPixCoord[0];
// get the smallest
float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR));
if (smallest > thick)
discard;
}
if (additionalAlpha == 0.0)
discard;
pixColor = getColorForCoord(v_texcoord);
pixColor.rgb *= pixColor[3];
pixColor *= alpha * additionalAlpha;
gl_FragColor = pixColor;
}
)#";0707010000004C000081A4000000000000000000000001688B5FE90000008C000000000000000000000000000000000000002700000000hyprlock-0.9.1/src/renderer/Shared.hpp#pragma once
#include "Texture.hpp"
#include "../defines.hpp"
struct SPreloadedAsset {
CTexture texture;
bool ready = false;
};0707010000004D000081A4000000000000000000000001688B5FE900000182000000000000000000000000000000000000002800000000hyprlock-0.9.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;
}
0707010000004E000081A4000000000000000000000001688B5FE900000221000000000000000000000000000000000000002800000000hyprlock-0.9.1/src/renderer/Texture.hpp#pragma once
#include <GLES3/gl32.h>
#include "../helpers/Math.hpp"
enum TEXTURETYPE {
TEXTURE_INVALID, // Invalid
TEXTURE_RGBA, // 4 channels
TEXTURE_RGBX, // discard A
TEXTURE_EXTERNAL, // EGLImage
};
class CTexture {
public:
CTexture();
~CTexture();
void destroyTexture();
void allocate();
TEXTURETYPE m_iType = TEXTURE_RGBA;
GLenum m_iTarget = GL_TEXTURE_2D;
bool m_bAllocated = false;
GLuint m_iTexID = 0;
Vector2D m_vSize;
};0707010000004F000081A4000000000000000000000001688B5FE9000016E8000000000000000000000000000000000000002400000000hyprlock-0.9.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;
}
07070100000050000041ED000000000000000000000002688B5FE900000000000000000000000000000000000000000000002400000000hyprlock-0.9.1/src/renderer/widgets07070100000051000081A4000000000000000000000001688B5FE90000308E000000000000000000000000000000000000003300000000hyprlock-0.9.1/src/renderer/widgets/Background.cpp#include "Background.hpp"
#include "../Renderer.hpp"
#include "../Framebuffer.hpp"
#include "../Shared.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../core/AnimationManager.hpp"
#include "../../config/ConfigManager.hpp"
#include <chrono>
#include <hyprlang.hpp>
#include <filesystem>
#include <memory>
#include <GLES3/gl32.h>
CBackground::CBackground() {
blurredFB = makeUnique<CFramebuffer>();
pendingBlurredFB = makeUnique<CFramebuffer>();
transformedScFB = makeUnique<CFramebuffer>();
}
CBackground::~CBackground() {
reset();
}
void CBackground::registerSelf(const ASP<CBackground>& self) {
m_self = self;
}
void CBackground::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
try {
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"));
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CBackground: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing propperty for CBackground: {}", e.what()); //
}
isScreenshot = path == "screenshot";
viewport = pOutput->getViewport();
outputPort = pOutput->stringPort;
transform = wlTransformToHyprutils(invertTransform(pOutput->transform));
scResourceID = CScreencopyFrame::getResourceId(pOutput);
g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (g_pRenderer->asyncResourceGatherer->gathered && !g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID)) {
Debug::log(LOG, "Missing screenshot for output {}", outputPort);
scResourceID = "";
}
if (isScreenshot) {
resourceID = scResourceID; // Fallback to solid background:color when scResourceID==""
if (!g_pHyprlock->getScreencopy()) {
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
resourceID = "";
}
} else if (!path.empty())
resourceID = "background:" + path;
if (!isScreenshot && reloadTime > -1) {
try {
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
plantReloadTimer(); // No reloads for screenshots.
}
}
void CBackground::reset() {
if (reloadTimer) {
reloadTimer->cancel();
reloadTimer.reset();
}
blurredFB->destroyBuffer();
pendingBlurredFB->destroyBuffer();
}
void CBackground::updatePrimaryAsset() {
if (asset || resourceID.empty())
return;
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset)
return;
const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->texture.m_vSize != viewport) && (!blurredFB->isAllocated() || firstRender);
if (NEEDFB)
renderToFB(asset->texture, *blurredFB, blurPasses);
}
void CBackground::updatePendingAsset() {
// For crossfading a new asset
if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated())
return;
renderToFB(pendingAsset->texture, *pendingBlurredFB, blurPasses);
}
void CBackground::updateScAsset() {
if (scAsset || scResourceID.empty())
return;
// path=screenshot -> scAsset = asset
scAsset = (asset && isScreenshot) ? asset : g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID);
if (!scAsset)
return;
const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL;
if (NEEDSCTRANSFORM)
renderToFB(scAsset->texture, *transformedScFB, 0, true);
}
const CTexture& CBackground::getPrimaryAssetTex() const {
// This case is only for background:path=screenshot with blurPasses=0
if (isScreenshot && blurPasses == 0 && transformedScFB->isAllocated())
return transformedScFB->m_cTex;
return (blurredFB->isAllocated()) ? blurredFB->m_cTex : asset->texture;
}
const CTexture& CBackground::getPendingAssetTex() const {
return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : pendingAsset->texture;
}
const CTexture& CBackground::getScAssetTex() const {
return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : scAsset->texture;
}
void CBackground::renderRect(CHyprColor color) {
CBox monbox = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderRect(monbox, color, 0);
}
static void onReloadTimer(AWP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG) {
PBG->onReloadTimerUpdate();
PBG->plantReloadTimer();
}
}
static void onAssetCallback(AWP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->startCrossFade();
}
static CBox getScaledBoxForTextureSize(const Vector2D& size, const Vector2D& viewport) {
CBox texbox = {{}, size};
float scaleX = viewport.x / size.x;
float scaleY = viewport.y / size.y;
texbox.w *= std::max(scaleX, scaleY);
texbox.h *= std::max(scaleX, scaleY);
if (scaleX > scaleY)
texbox.y = -(texbox.h - viewport.y) / 2.f;
else
texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round();
return texbox;
}
void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, bool applyTransform) {
if (firstRender)
firstRender = false;
// make it brah
Vector2D size = tex.m_vSize;
if (applyTransform && transform % 2 == 1) {
size.x = tex.m_vSize.y;
size.y = tex.m_vSize.x;
}
const auto TEXBOX = getScaledBoxForTextureSize(size, viewport);
if (!fb.isAllocated())
fb.alloc(viewport.x, viewport.y); // TODO 10 bit
fb.bind();
g_pRenderer->renderTexture(TEXBOX, tex, 1.0, 0, applyTransform ? transform : HYPRUTILS_TRANSFORM_NORMAL);
if (blurPasses > 0)
g_pRenderer->blurFB(fb,
CRenderer::SBlurParams{
.size = blurSize,
.passes = passes,
.noise = noise,
.contrast = contrast,
.brightness = brightness,
.vibrancy = vibrancy,
.vibrancy_darkness = vibrancy_darkness,
});
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
bool CBackground::draw(const SRenderData& data) {
updatePrimaryAsset();
updatePendingAsset();
updateScAsset();
if (asset && asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
resourceID = "";
renderRect(color);
return false;
}
if (!asset || resourceID.empty()) {
// fade in/out with a solid color
if (data.opacity < 1.0 && scAsset) {
const auto& SCTEX = getScAssetTex();
const auto SCTEXBOX = getScaledBoxForTextureSize(SCTEX.m_vSize, viewport);
g_pRenderer->renderTexture(SCTEXBOX, SCTEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
CHyprColor col = color;
col.a *= data.opacity;
renderRect(col);
return true;
}
renderRect(color);
return !asset && !resourceID.empty(); // resource not ready
}
const auto& TEX = getPrimaryAssetTex();
const auto TEXBOX = getScaledBoxForTextureSize(TEX.m_vSize, viewport);
if (data.opacity < 1.0 && scAsset) {
const auto& SCTEX = getScAssetTex();
g_pRenderer->renderTextureMix(TEXBOX, SCTEX, TEX, 1.0, data.opacity, 0);
} else if (crossFadeProgress->isBeingAnimated()) {
const auto& PENDINGTEX = getPendingAssetTex();
g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0);
} else
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
}
void CBackground::plantReloadTimer() {
if (reloadTime == 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
else if (reloadTime > 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
}
void CBackground::onReloadTimerUpdate() {
const std::string OLDPATH = path;
// Path parsing and early returns
if (!reloadCommand.empty()) {
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
if (path.starts_with("file://"))
path = path.substr(7);
if (path.empty())
return;
}
try {
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
if (OLDPATH == path && MTIME == modificationTime)
return;
modificationTime = MTIME;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
return;
// Issue the next request
request.id = std::string{"background:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count());
pendingResourceID = request.id;
request.asset = path;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CBackground::startCrossFade() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
Debug::log(ERR, "New asset had an invalid texture!");
pendingResourceID = "";
} else if (resourceID != pendingResourceID) {
pendingAsset = newAsset;
crossFadeProgress->setValueAndWarp(0);
*crossFadeProgress = 1.0;
crossFadeProgress->setCallbackOnEnd(
[REF = m_self](auto) {
if (const auto PSELF = REF.lock()) {
PSELF->asset = PSELF->pendingAsset;
PSELF->pendingAsset = nullptr;
g_pRenderer->asyncResourceGatherer->unloadAsset(PSELF->pendingAsset);
PSELF->resourceID = PSELF->pendingResourceID;
PSELF->pendingResourceID = "";
PSELF->blurredFB->destroyBuffer();
PSELF->blurredFB = std::move(PSELF->pendingBlurredFB);
}
},
true);
g_pHyprlock->renderOutput(outputPort);
}
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
}
07070100000052000081A4000000000000000000000001688B5FE900000CB9000000000000000000000000000000000000003300000000hyprlock-0.9.1/src/renderer/widgets/Background.hpp#pragma once
#include "IWidget.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "../Framebuffer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <cstdint>
#include <hyprutils/math/Misc.hpp>
#include <string>
#include <unordered_map>
#include <any>
#include <chrono>
#include <filesystem>
struct SPreloadedAsset;
class COutput;
class CBackground : public IWidget {
public:
CBackground();
~CBackground();
void registerSelf(const ASP<CBackground>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
void reset(); // Unload assets, remove timers, etc.
void updatePrimaryAsset();
void updatePendingAsset();
void updateScAsset();
const CTexture& getPrimaryAssetTex() const;
const CTexture& getPendingAssetTex() const;
const CTexture& getScAssetTex() const;
void renderRect(CHyprColor color);
void renderToFB(const CTexture& text, CFramebuffer& fb, int passes, bool applyTransform = false);
void onReloadTimerUpdate();
void plantReloadTimer();
void startCrossFade();
private:
AWP<CBackground> m_self;
// if needed
UP<CFramebuffer> blurredFB;
UP<CFramebuffer> pendingBlurredFB;
UP<CFramebuffer> transformedScFB;
int blurSize = 10;
int blurPasses = 3;
float noise = 0.0117;
float contrast = 0.8916;
float brightness = 0.8172;
float vibrancy = 0.1696;
float vibrancy_darkness = 0.0;
Vector2D viewport;
std::string path = "";
std::string outputPort;
Hyprutils::Math::eTransform transform;
std::string resourceID;
std::string scResourceID;
std::string pendingResourceID;
PHLANIMVAR<float> crossFadeProgress;
CHyprColor color;
SPreloadedAsset* asset = nullptr;
SPreloadedAsset* scAsset = nullptr;
SPreloadedAsset* pendingAsset = nullptr;
bool isScreenshot = false;
bool firstRender = true;
int reloadTime = -1;
std::string reloadCommand;
CAsyncResourceGatherer::SPreloadRequest request;
ASP<CTimer> reloadTimer;
std::filesystem::file_time_type modificationTime;
};
07070100000053000081A4000000000000000000000001688B5FE9000025F6000000000000000000000000000000000000003000000000hyprlock-0.9.1/src/renderer/widgets/IWidget.cpp#include "IWidget.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
#include <chrono>
#include <unistd.h>
#include <pwd.h>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::String;
#if defined(_LIBCPP_VERSION)
#pragma comment(lib, "date-tz")
#include <date/tz.h>
namespace std {
namespace chrono {
using date::current_zone;
using date::locate_zone;
using date::time_zone;
}
}
#endif
static Vector2D rotateVector(const Vector2D& vec, const double& ang) {
const double COS = std::abs(std::cos(ang));
const double SIN = std::abs(std::sin(ang));
return Vector2D((vec.x * COS) + (vec.y * SIN), (vec.x * SIN) + (vec.y * COS));
}
Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang) {
// offset after rotation for alignment
Vector2D rot;
if (ang != 0)
rot = (size - rotateVector(size, ang)) / 2.0;
Vector2D pos = offset;
if (halign == "center")
pos.x += viewport.x / 2.0 - size.x / 2.0;
else if (halign == "left")
pos.x += 0 - rot.x;
else if (halign == "right")
pos.x += viewport.x - size.x + rot.x;
else if (halign != "none")
Debug::log(ERR, "IWidget: invalid halign {}", halign);
if (valign == "center")
pos.y += viewport.y / 2.0 - size.y / 2.0;
else if (valign == "top")
pos.y += viewport.y - size.y + rot.y;
else if (valign == "bottom")
pos.y += 0 - rot.y;
else if (valign != "none")
Debug::log(ERR, "IWidget: invalid valign {}", valign);
return pos;
}
int IWidget::roundingForBox(const CBox& box, int roundingConfig) {
const int MINHALFBOX = std::min(box.w, box.h) / 2.0;
if (roundingConfig == -1)
return MINHALFBOX;
return std::clamp(roundingConfig, 0, MINHALFBOX);
}
int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness) {
const int MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0;
if (roundingConfig == -1)
return MINHALFBORDER;
else if (roundingConfig == 0)
return 0;
return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER);
}
static void replaceAllAttempts(std::string& str) {
const size_t ATTEMPTS = g_pAuth->getFailedAttempts();
const std::string STR = std::to_string(ATTEMPTS);
size_t pos = 0;
while ((pos = str.find("$ATTEMPTS", pos)) != std::string::npos) {
if (str.substr(pos, 10).ends_with('[') && str.substr(pos).contains(']')) {
const std::string REPL = str.substr(pos + 10, str.find_first_of(']', pos) - 10 - pos);
if (ATTEMPTS == 0) {
str.replace(pos, 11 + REPL.length(), REPL);
pos += REPL.length();
} else {
str.replace(pos, 11 + REPL.length(), STR);
pos += STR.length();
}
} else {
str.replace(pos, 9, STR);
pos += STR.length();
}
}
}
static void replaceAllLayout(std::string& str) {
std::string layoutName = "error";
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
if (g_pSeatManager->m_pXKBKeymap) {
const auto PNAME = xkb_keymap_layout_get_name(g_pSeatManager->m_pXKBKeymap, LAYOUTIDX);
if (PNAME)
layoutName = PNAME;
}
size_t pos = 0;
while ((pos = str.find("$LAYOUT", pos)) != std::string::npos) {
if (str.substr(pos, 8).ends_with('[') && str.substr(pos).contains(']')) {
const std::string REPL = str.substr(pos + 8, str.find_first_of(']', pos) - 8 - pos);
const CVarList LANGS(REPL);
if (LAYOUTIDX >= LANGS.size()) {
Debug::log(ERR, "Layout index {} out of bounds. Max is {}.", LAYOUTIDX, LANGS.size() - 1);
return;
}
const std::string LANG = LANGS[LAYOUTIDX].empty() ? layoutName : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
str.replace(pos, 9 + REPL.length(), LANG);
pos += LANG.length();
} else {
str.replace(pos, 7, layoutName);
pos += layoutName.length();
}
}
}
static bool logMissingTzOnce = true;
static std::chrono::hh_mm_ss<std::chrono::system_clock::duration> getTime() {
const std::chrono::time_zone* pCurrentTz = nullptr;
try {
auto name = std::getenv("TZ");
if (name)
pCurrentTz = std::chrono::locate_zone(name);
} catch (std::runtime_error&) { Debug::log(WARN, "Invalid TZ value. Falling back to current timezone!"); }
if (!pCurrentTz)
pCurrentTz = std::chrono::current_zone();
const auto TPNOW = std::chrono::system_clock::now();
//
std::chrono::hh_mm_ss<std::chrono::system_clock::duration> hhmmss;
if (!pCurrentTz) {
if (logMissingTzOnce) {
Debug::log(WARN, "Current timezone unknown. Falling back to UTC!");
logMissingTzOnce = false;
}
hhmmss = std::chrono::hh_mm_ss{TPNOW - std::chrono::floor<std::chrono::days>(TPNOW)};
} else
hhmmss = std::chrono::hh_mm_ss{pCurrentTz->to_local(TPNOW) - std::chrono::floor<std::chrono::days>(pCurrentTz->to_local(TPNOW))};
return hhmmss;
}
static std::string getTime24h() {
const auto HHMMSS = getTime();
const auto HRS = HHMMSS.hours().count();
const auto MINS = HHMMSS.minutes().count();
return (HRS < 10 ? "0" : "") + std::to_string(HRS) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS);
}
static std::string getTime12h() {
const auto HHMMSS = getTime();
const auto HRS = HHMMSS.hours().count();
const auto MINS = HHMMSS.minutes().count();
return (HRS == 12 || HRS == 0 ? "12" : (HRS % 12 < 10 ? "0" : "") + std::to_string(HRS % 12)) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS) +
(HRS < 12 ? " AM" : " PM");
}
IWidget::SFormatResult IWidget::formatString(std::string in) {
auto uidPassword = getpwuid(getuid());
char* username = uidPassword->pw_name;
char* user_gecos = uidPassword->pw_gecos;
if (!username)
Debug::log(ERR, "Error in formatString, username null. Errno: ", errno);
if (!user_gecos)
Debug::log(WARN, "Error in formatString, user_gecos null. Errno: ", errno);
IWidget::SFormatResult result;
replaceInString(in, "$DESC", std::string{user_gecos ? user_gecos : ""});
replaceInString(in, "$USER", std::string{username ? username : ""});
replaceInString(in, "<br/>", std::string{"\n"});
if (in.contains("$TIME12")) {
replaceInString(in, "$TIME12", getTime12h());
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
}
if (in.contains("$TIME")) {
replaceInString(in, "$TIME", getTime24h());
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
}
if (in.contains("$ATTEMPTS")) {
replaceAllAttempts(in);
result.allowForceUpdate = true;
}
if (in.contains("$LAYOUT")) {
replaceAllLayout(in);
result.allowForceUpdate = true;
}
if (in.contains("$FAIL")) {
const auto FAIL = g_pAuth->getCurrentFailText();
replaceInString(in, "$FAIL", FAIL);
result.allowForceUpdate = true;
}
if (in.contains("$PAMFAIL")) {
const auto FAIL = g_pAuth->getFailText(AUTH_IMPL_PAM);
replaceInString(in, "$PAMFAIL", FAIL.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$PAMPROMPT")) {
const auto PROMPT = g_pAuth->getPrompt(AUTH_IMPL_PAM);
replaceInString(in, "$PAMPROMPT", PROMPT.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$FPRINTFAIL")) {
const auto FPRINTFAIL = g_pAuth->getFailText(AUTH_IMPL_FINGERPRINT);
replaceInString(in, "$FPRINTFAIL", FPRINTFAIL.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$FPRINTPROMPT")) {
const auto FPRINTPROMPT = g_pAuth->getPrompt(AUTH_IMPL_FINGERPRINT);
replaceInString(in, "$FPRINTPROMPT", FPRINTPROMPT.value_or(""));
result.allowForceUpdate = true;
}
if (in.starts_with("cmd[") && in.contains("]")) {
// this is a command
CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true);
for (const auto& v : vars) {
if (v.starts_with("update:")) {
try {
if (v.substr(7).contains(':')) {
auto str = v.substr(v.substr(7).find_first_of(':') + 8);
result.allowForceUpdate = str == "true" || std::stoull(str) == 1;
}
result.updateEveryMs = std::stoull(v.substr(7));
} catch (std::exception& e) { Debug::log(ERR, "Error parsing {} in cmd[]", v); }
} else {
Debug::log(ERR, "Unknown prop in string format {}", v);
}
}
result.alwaysUpdate = true;
in = in.substr(in.find_first_of(']') + 1);
result.cmd = true;
}
result.formatted = in;
return result;
}
void IWidget::setHover(bool hover) {
hovered = hover;
}
bool IWidget::isHovered() const {
return hovered;
}
bool IWidget::containsPoint(const Vector2D& pos) const {
return getBoundingBoxWl().containsPoint(pos);
}
07070100000054000081A4000000000000000000000001688B5FE90000065B000000000000000000000000000000000000003000000000hyprlock-0.9.1/src/renderer/widgets/IWidget.hpp#pragma once
#include "../../defines.hpp"
#include "../../helpers/Math.hpp"
#include <string>
#include <unordered_map>
#include <any>
class COutput;
class IWidget {
public:
struct SRenderData {
float opacity = 1;
};
virtual ~IWidget() = default;
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
virtual bool draw(const SRenderData& data) = 0;
static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
const double& ang = 0);
static int roundingForBox(const CBox& box, int roundingConfig);
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
virtual CBox getBoundingBoxWl() const {
return CBox();
};
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}
virtual void onHover(const Vector2D& pos) {}
bool containsPoint(const Vector2D& pos) const;
struct SFormatResult {
std::string formatted;
float updateEveryMs = 0; // 0 means don't (static)
bool alwaysUpdate = false;
bool cmd = false;
bool allowForceUpdate = false;
};
static SFormatResult formatString(std::string in);
void setHover(bool hover);
bool isHovered() const;
private:
bool hovered = false;
};
07070100000055000081A4000000000000000000000001688B5FE9000020AC000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Image.cpp#include "Image.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
CImage::~CImage() {
reset();
}
void CImage::registerSelf(const ASP<CImage>& self) {
m_self = self;
}
static void onTimer(AWP<CImage> ref) {
if (auto PIMAGE = ref.lock(); PIMAGE) {
PIMAGE->onTimerUpdate();
PIMAGE->plantTimer();
}
}
static void onAssetCallback(AWP<CImage> ref) {
if (auto PIMAGE = ref.lock(); PIMAGE)
PIMAGE->renderUpdate();
}
void CImage::onTimerUpdate() {
const std::string OLDPATH = path;
if (!reloadCommand.empty()) {
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
if (path.starts_with("file://"))
path = path.substr(7);
if (path.empty())
return;
}
try {
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
if (OLDPATH == path && MTIME == modificationTime)
return;
modificationTime = MTIME;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
return;
request.id = std::string{"image:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count());
pendingResourceID = request.id;
request.asset = path;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CImage::plantTimer() {
if (reloadTime == 0) {
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, true);
} else if (reloadTime > 0)
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, false);
}
void CImage::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
viewport = pOutput->getViewport();
stringPort = pOutput->stringPort;
shadow.configure(m_self, props, viewport);
try {
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 = *CGradientValueData::fromAnyPv(props.at("border_color"));
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(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"));
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"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CImage: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing propperty for CImage: {}", e.what()); //
}
resourceID = "image:" + path;
angle = angle * M_PI / 180.0;
if (reloadTime > -1) {
try {
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
plantTimer();
}
}
void CImage::reset() {
if (imageTimer) {
imageTimer->cancel();
imageTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
imageFB.destroyBuffer();
if (asset && reloadTime > -1) // Don't unload asset if it's a static image
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = nullptr;
pendingResourceID = "";
resourceID = "";
}
bool CImage::draw(const SRenderData& data) {
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);
// plus borders if any
CBox borderBox = {angle == 0 ? BORDERPOS : BORDERPOS + Vector2D{1.0, 1.0}, texbox.size() + IMAGEPOS * 2.0};
borderBox.round();
const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0};
const int ROUND = roundingForBox(texbox, rounding);
const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border);
imageFB.alloc(FBSIZE.x, FBSIZE.y, true);
g_pRenderer->pushFb(imageFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (border > 0)
g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0);
texbox.round();
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
g_pRenderer->popFb();
}
CTexture* tex = &imageFB.m_cTex;
CBox texbox = {{}, tex->m_vSize};
if (firstRender) {
firstRender = false;
shadow.markShadowDirty();
}
shadow.draw(data);
pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle);
texbox.x = pos.x;
texbox.y = pos.y;
texbox.round();
texbox.rot = angle;
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return data.opacity < 1.0;
}
void CImage::renderUpdate() {
auto newAsset = g_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.destroyBuffer();
asset = newAsset;
resourceID = pendingResourceID;
firstRender = true;
}
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
g_pHyprlock->renderOutput(stringPort);
}
CBox CImage::getBoundingBoxWl() const {
if (!imageFB.isAllocated())
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - imageFB.m_cTex.m_vSize.y},
imageFB.m_cTex.m_vSize,
};
}
void CImage::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CImage::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}
07070100000056000081A4000000000000000000000001688B5FE9000008F2000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Image.hpp#pragma once
#include "IWidget.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include "Shadowable.hpp"
#include <string>
#include <filesystem>
#include <unordered_map>
#include <any>
struct SPreloadedAsset;
class COutput;
class CImage : public IWidget {
public:
CImage() = default;
~CImage();
void registerSelf(const ASP<CImage>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
AWP<CImage> m_self;
CFramebuffer imageFB;
int size;
int rounding;
double border;
double angle;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
std::string halign, valign, path;
bool firstRender = true;
int reloadTime;
std::string reloadCommand;
std::string onclickCommand;
std::filesystem::file_time_type modificationTime;
ASP<CTimer> imageTimer;
CAsyncResourceGatherer::SPreloadRequest request;
Vector2D viewport;
std::string stringPort;
std::string resourceID;
std::string pendingResourceID; // if reloading image
SPreloadedAsset* asset = nullptr;
CShadowable shadow;
};
07070100000057000081A4000000000000000000000001688B5FE9000018D4000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Label.cpp#include "Label.hpp"
#include "../Renderer.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include <hyprlang.hpp>
#include <stdexcept>
CLabel::~CLabel() {
reset();
}
void CLabel::registerSelf(const ASP<CLabel>& self) {
m_self = self;
}
static void onTimer(AWP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL) {
// update label
PLABEL->onTimerUpdate();
// plant new timer
PLABEL->plantTimer();
}
}
static void onAssetCallback(AWP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL)
PLABEL->renderUpdate();
}
std::string CLabel::getUniqueResourceId() {
return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
}
void CLabel::onTimerUpdate() {
std::string oldFormatted = label.formatted;
label = formatString(labelPreFormat);
if (label.formatted == oldFormatted && !label.alwaysUpdate)
return;
if (!pendingResourceID.empty()) {
Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID);
return;
}
// request new
request.id = getUniqueResourceId();
pendingResourceID = request.id;
request.asset = label.formatted;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CLabel::plantTimer() {
if (label.updateEveryMs != 0)
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), [REF = m_self](auto, auto) { onTimer(REF); }, this, label.allowForceUpdate);
else if (label.updateEveryMs == 0 && label.allowForceUpdate)
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, this, true);
}
void CLabel::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
shadow.configure(m_self, props, viewport);
try {
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text"));
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;
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
CHyprColor 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;
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CLabel: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CLabel: {}", e.what()); //
}
pos = configPos; // Label size not known yet
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
plantTimer();
}
void CLabel::reset() {
if (labelTimer) {
labelTimer->cancel();
labelTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
if (asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = nullptr;
pendingResourceID.clear();
resourceID.clear();
}
bool CLabel::draw(const SRenderData& data) {
if (!asset) {
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset)
return true;
}
if (updateShadow) {
updateShadow = false;
shadow.markShadowDirty();
}
shadow.draw(data);
// calc pos
pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle);
CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y};
box.rot = angle;
g_pRenderer->renderTexture(box, asset->texture, data.opacity);
return false;
}
void CLabel::renderUpdate() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
// new asset is ready :D
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = newAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
updateShadow = true;
} else {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
return;
}
g_pHyprlock->renderOutput(outputStringPort);
}
CBox CLabel::getBoundingBoxWl() const {
if (!asset)
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y},
asset->texture.m_vSize,
};
}
void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CLabel::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}
07070100000058000081A4000000000000000000000001688B5FE90000079C000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Label.hpp#pragma once
#include "IWidget.hpp"
#include "Shadowable.hpp"
#include "../../helpers/Math.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() = default;
~CLabel();
void registerSelf(const ASP<CLabel>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
AWP<CLabel> m_self;
std::string getUniqueResourceId();
std::string labelPreFormat;
IWidget::SFormatResult label;
Vector2D viewport;
Vector2D pos;
Vector2D configPos;
double angle;
std::string resourceID;
std::string pendingResourceID; // if dynamic label
std::string halign, valign;
std::string onclickCommand;
SPreloadedAsset* asset = nullptr;
std::string outputStringPort;
CAsyncResourceGatherer::SPreloadRequest request;
ASP<CTimer> labelTimer = nullptr;
CShadowable shadow;
bool updateShadow = true;
};
07070100000059000081A4000000000000000000000001688B5FE900004E50000000000000000000000000000000000000003B00000000hyprlock-0.9.1/src/renderer/widgets/PasswordInputField.cpp#include "PasswordInputField.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../config/ConfigManager.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/AnimationManager.hpp"
#include "../../helpers/Color.hpp"
#include <cmath>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/String.hpp>
#include <algorithm>
#include <hyprlang.hpp>
using namespace Hyprutils::String;
CPasswordInputField::~CPasswordInputField() {
reset();
}
void CPasswordInputField::registerSelf(const ASP<CPasswordInputField>& self) {
m_self = self;
}
void CPasswordInputField::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
shadow.configure(m_self, props, viewport);
try {
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
configSize = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
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"));
dots.textFormat = std::any_cast<Hyprlang::STRING>(props.at("dots_text_format"));
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"));
fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color"));
colorConfig.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
colorConfig.font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
colorConfig.fail = CGradientValueData::fromAnyPv(props.at("fail_color"));
colorConfig.check = CGradientValueData::fromAnyPv(props.at("check_color"));
colorConfig.both = CGradientValueData::fromAnyPv(props.at("bothlock_color"));
colorConfig.caps = CGradientValueData::fromAnyPv(props.at("capslock_color"));
colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color"));
colorConfig.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
colorConfig.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
colorConfig.hiddenBase = std::any_cast<Hyprlang::INT>(props.at("hide_input_base_color"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CPasswordInputField: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CPasswordInputField: {}", e.what()); //
}
configPos = pos;
colorState.font = colorConfig.font;
pos = posFromHVAlign(viewport, configSize, pos, halign, valign);
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
g_pAnimationManager->createAnimation(0.f, fade.a, g_pConfigManager->m_AnimationTree.getConfig("inputFieldFade"));
g_pAnimationManager->createAnimation(0.f, dots.currentAmount, g_pConfigManager->m_AnimationTree.getConfig("inputFieldDots"));
g_pAnimationManager->createAnimation(configSize, size, g_pConfigManager->m_AnimationTree.getConfig("inputFieldWidth"));
g_pAnimationManager->createAnimation(colorConfig.inner, colorState.inner, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
g_pAnimationManager->createAnimation(*colorConfig.outer, colorState.outer, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
srand(std::chrono::system_clock::now().time_since_epoch().count());
pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign);
if (!dots.textFormat.empty()) {
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
CAsyncResourceGatherer::SPreloadRequest request;
request.id = dots.textResourceID;
request.asset = dots.textFormat;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorConfig.font;
request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
// request the inital placeholder asset
updatePlaceholder();
}
void CPasswordInputField::reset() {
if (fade.fadeOutTimer.get()) {
fade.fadeOutTimer->cancel();
fade.fadeOutTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
if (placeholder.asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
placeholder.asset = nullptr;
placeholder.resourceID.clear();
placeholder.currentText.clear();
}
static void fadeOutCallback(AWP<CPasswordInputField> ref) {
if (const auto PP = ref.lock(); PP)
PP->onFadeOutTimer();
}
void CPasswordInputField::onFadeOutTimer() {
fade.allowFadeOut = true;
fade.fadeOutTimer.reset();
g_pHyprlock->renderOutput(outputStringPort);
}
void CPasswordInputField::updateFade() {
if (!fadeOnEmpty) {
fade.a->setValueAndWarp(1.0);
return;
}
const bool INPUTUSED = passwordLength > 0 || checkWaiting;
if (INPUTUSED && fade.allowFadeOut)
fade.allowFadeOut = false;
if (INPUTUSED && fade.fadeOutTimer.get()) {
fade.fadeOutTimer->cancel();
fade.fadeOutTimer.reset();
}
if (!INPUTUSED && fade.a->goal() != 0.0) {
if (fade.allowFadeOut || fadeTimeoutMs == 0) {
*fade.a = 0.0;
fade.allowFadeOut = false;
} else if (!fade.fadeOutTimer.get())
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr);
} else if (INPUTUSED && fade.a->goal() != 1.0)
*fade.a = 1.0;
if (fade.a->isBeingAnimated())
redrawShadow = true;
}
void CPasswordInputField::updateDots() {
if (dots.currentAmount->goal() == passwordLength)
return;
if (checkWaiting)
return;
if (passwordLength == 0)
dots.currentAmount->setValueAndWarp(passwordLength);
else
*dots.currentAmount = passwordLength;
}
bool CPasswordInputField::draw(const SRenderData& data) {
if (firstRender || redrawShadow) {
firstRender = false;
redrawShadow = false;
shadow.markShadowDirty();
}
bool forceReload = false;
passwordLength = g_pHyprlock->getPasswordBufferDisplayLen();
checkWaiting = g_pAuth->checkWaiting();
displayFail = g_pAuth->m_bDisplayFailText;
updateFade();
updateDots();
updateColors();
updatePlaceholder();
updateWidth();
updateHiddenInputState();
CBox inputFieldBox = {pos, size->value()};
CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}};
SRenderData shadowData = data;
shadowData.opacity *= fade.a->value();
if (!size->isBeingAnimated())
shadow.draw(shadowData);
//CGradientValueData outerGrad = colorState.outer->value();
//for (auto& c : outerGrad.m_vColors)
// c.a *= fade.a->value() * data.opacity;
CHyprColor innerCol = colorState.inner->value();
innerCol.a *= fade.a->value() * data.opacity;
CHyprColor fontCol = colorState.font;
fontCol.a *= fade.a->value() * data.opacity;
if (outThick > 0) {
const auto OUTERROUND = roundingForBorderBox(outerBox, rounding, outThick);
g_pRenderer->renderBorder(outerBox, colorState.outer->value(), outThick, OUTERROUND, fade.a->value() * data.opacity);
if (passwordLength != 0 && !checkWaiting && hiddenInputState.enabled) {
CBox outerBoxScaled = outerBox;
Vector2D p = outerBox.pos();
outerBoxScaled.translate(-p).scale(0.5).translate(p);
if (hiddenInputState.lastQuadrant > 1)
outerBoxScaled.y += outerBoxScaled.h;
if (hiddenInputState.lastQuadrant % 2 == 1)
outerBoxScaled.x += outerBoxScaled.w;
glEnable(GL_SCISSOR_TEST);
glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h);
g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
}
}
const int ROUND = roundingForBox(inputFieldBox, rounding);
g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND);
if (!hiddenInputState.enabled) {
const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f;
Vector2D passSize{RECTPASSSIZE, RECTPASSSIZE};
int passSpacing = std::floor(passSize.x * dots.spacing);
if (!dots.textFormat.empty()) {
if (!dots.textAsset)
dots.textAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(dots.textResourceID);
if (!dots.textAsset)
forceReload = true;
else {
passSize = dots.textAsset->texture.m_vSize;
passSpacing = std::floor(passSize.x * dots.spacing);
}
}
const auto CURRDOTS = dots.currentAmount->value();
const double DOTPAD = (inputFieldBox.h - passSize.y) / 2.0;
const double DOTAREAWIDTH = inputFieldBox.w - (DOTPAD * 2);
const int MAXDOTS = std::round(DOTAREAWIDTH * 1.0 / (passSize.x + passSpacing));
const int DOTFLOORED = std::floor(CURRDOTS);
const auto DOTALPHA = fontCol.a;
// Calculate the total width required for all dots including spaces between them
const double CURRWIDTH = ((passSize.x + passSpacing) * CURRDOTS) - passSpacing;
// Calculate starting x-position to ensure dots stay centered within the input field
double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD : DOTPAD;
if (CURRDOTS > MAXDOTS)
xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2.0;
if (dots.rounding == -1)
dots.rounding = passSize.x / 2.0;
else if (dots.rounding == -2)
dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size;
for (int i = 0; i < CURRDOTS; ++i) {
if (i < DOTFLOORED - MAXDOTS)
continue;
if (CURRDOTS != DOTFLOORED) {
if (i == DOTFLOORED)
fontCol.a *= (CURRDOTS - DOTFLOORED) * data.opacity;
else if (i == DOTFLOORED - MAXDOTS)
fontCol.a *= (1 - CURRDOTS + DOTFLOORED) * data.opacity;
}
Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart + (i * (passSize.x + passSpacing)), (inputFieldBox.h / 2.0) - (passSize.y / 2.0)};
CBox box{dotPosition, passSize};
if (!dots.textFormat.empty()) {
if (!dots.textAsset) {
forceReload = true;
fontCol.a = DOTALPHA;
break;
}
g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding);
} else
g_pRenderer->renderRect(box, fontCol, dots.rounding);
fontCol.a = DOTALPHA;
}
}
if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) {
SPreloadedAsset* currAsset = nullptr;
if (!placeholder.asset)
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
currAsset = placeholder.asset;
if (currAsset) {
const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->texture.m_vSize / 2.0;
const CBox ASSETBOX{ASSETPOS, currAsset->texture.m_vSize};
// Cut the texture to the width of the input field
glEnable(GL_SCISSOR_TEST);
glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h);
g_pRenderer->renderTexture(ASSETBOX, currAsset->texture, data.opacity * fade.a->value(), 0);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
} else
forceReload = true;
}
return redrawShadow || forceReload;
}
void CPasswordInputField::updatePlaceholder() {
if (passwordLength != 0) {
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) {
std::erase(placeholder.registeredResourceIDs, placeholder.resourceID);
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
placeholder.asset = nullptr;
placeholder.resourceID = "";
redrawShadow = true;
}
return;
}
// already requested a placeholder for the current fail
if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts())
return;
placeholder.failedAttempts = g_pAuth->getFailedAttempts();
std::string newText = (displayFail) ? formatString(configFailText).formatted : formatString(configPlaceholderText).formatted;
// if the text is unchanged we don't need to do anything, unless we are swapping font color
const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont;
if (!ALLOWCOLORSWAP && newText == placeholder.currentText)
return;
const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a);
if (placeholder.resourceID == NEWRESOURCEID)
return;
Debug::log(TRACE, "Updating placeholder text: {}", newText);
placeholder.currentText = newText;
placeholder.asset = nullptr;
placeholder.resourceID = NEWRESOURCEID;
if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end())
return;
Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID);
placeholder.registeredResourceIDs.push_back(placeholder.resourceID);
// query
CAsyncResourceGatherer::SPreloadRequest request;
request.id = placeholder.resourceID;
request.asset = placeholder.currentText;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)size->value().y / 4;
request.callback = [REF = m_self] {
if (const auto SELF = REF.lock(); SELF)
g_pHyprlock->renderOutput(SELF->outputStringPort);
};
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CPasswordInputField::updateWidth() {
double targetSizeX = configSize.x;
if (passwordLength == 0 && placeholder.asset)
targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y;
targetSizeX = std::max(targetSizeX, configSize.x);
if (size->goal().x != targetSizeX) {
*size = Vector2D{targetSizeX, configSize.y};
size->setCallbackOnEnd([this](auto) {
redrawShadow = true;
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
});
}
if (size->isBeingAnimated()) {
redrawShadow = true;
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
}
}
void CPasswordInputField::updateHiddenInputState() {
if (!hiddenInputState.enabled || (size_t)hiddenInputState.lastPasswordLength == passwordLength)
return;
// randomize new thang
hiddenInputState.lastPasswordLength = passwordLength;
const auto BASEOK = colorConfig.hiddenBase.asOkLab();
// convert to polar coordinates
const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2));
// now randomly rotate the hue
const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4;
// convert back to OkLab
const Hyprgraphics::CColor newColor = Hyprgraphics::CColor::SOkLab{
.l = BASEOK.l,
.a = OKICHCHROMA * std::cos(OKICHHUE),
.b = OKICHCHROMA * std::sin(OKICHHUE),
};
hiddenInputState.lastColor = {newColor, 1.0};
hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4;
}
void CPasswordInputField::updateColors() {
const bool BORDERLESS = outThick == 0;
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
CGradientValueData* targetGrad = nullptr;
if (g_pHyprlock->m_bCapsLock && NUMLOCK && !colorConfig.both->m_bIsFallback)
targetGrad = colorConfig.both;
else if (g_pHyprlock->m_bCapsLock)
targetGrad = colorConfig.caps;
else if (NUMLOCK && !colorConfig.num->m_bIsFallback)
targetGrad = colorConfig.num;
if (checkWaiting)
targetGrad = colorConfig.check;
else if (displayFail && passwordLength == 0)
targetGrad = colorConfig.fail;
CGradientValueData* outerTarget = colorConfig.outer;
CHyprColor innerTarget = colorConfig.inner;
CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
if (targetGrad) {
if (BORDERLESS && colorConfig.swapFont) {
fontTarget = targetGrad->m_vColors.front();
} else if (BORDERLESS && !colorConfig.swapFont) {
innerTarget = targetGrad->m_vColors.front();
// When changing the inner color, the font cannot be fail_color
fontTarget = colorConfig.font;
} else if (targetGrad) {
outerTarget = targetGrad;
}
}
if (!BORDERLESS && *outerTarget != colorState.outer->goal())
*colorState.outer = *outerTarget;
if (innerTarget != colorState.inner->goal())
*colorState.inner = innerTarget;
colorState.font = fontTarget;
}
CBox CPasswordInputField::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size->value().y},
size->value(),
};
}
void CPasswordInputField::onHover(const Vector2D& pos) {
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
}
0707010000005A000081A4000000000000000000000001688B5FE900000FBA000000000000000000000000000000000000003B00000000hyprlock-0.9.1/src/renderer/widgets/PasswordInputField.hpp#pragma once
#include "IWidget.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "Shadowable.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <vector>
#include <any>
#include <unordered_map>
struct SPreloadedAsset;
class CPasswordInputField : public IWidget {
public:
CPasswordInputField() = default;
virtual ~CPasswordInputField();
void registerSelf(const ASP<CPasswordInputField>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onHover(const Vector2D& pos);
virtual CBox getBoundingBoxWl() const;
void reset();
void onFadeOutTimer();
private:
AWP<CPasswordInputField> m_self;
void updateDots();
void updateFade();
void updatePlaceholder();
void updateWidth();
void updateHiddenInputState();
void updateInputState();
void updateColors();
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = false;
bool displayFail = false;
size_t passwordLength = 0;
PHLANIMVAR<Vector2D> size;
Vector2D pos;
Vector2D viewport;
Vector2D configPos;
Vector2D configSize;
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
uint64_t configFailTimeoutMs = 2000;
int outThick, rounding;
struct {
PHLANIMVAR<float> currentAmount;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
std::string textFormat = "";
std::string textResourceID;
SPreloadedAsset* textAsset = nullptr;
} dots;
struct {
PHLANIMVAR<float> a;
bool appearing = true;
ASP<CTimer> fadeOutTimer = nullptr;
bool allowFadeOut = false;
} fade;
struct {
std::string resourceID = "";
SPreloadedAsset* asset = nullptr;
std::string currentText = "";
size_t failedAttempts = 0;
std::vector<std::string> registeredResourceIDs;
} placeholder;
struct {
CHyprColor lastColor;
int lastQuadrant = 0;
int lastPasswordLength = 0;
bool enabled = false;
} hiddenInputState;
struct {
CGradientValueData* outer = nullptr;
CHyprColor inner;
CHyprColor font;
CGradientValueData* fail = nullptr;
CGradientValueData* check = nullptr;
CGradientValueData* caps = nullptr;
CGradientValueData* num = nullptr;
CGradientValueData* both = nullptr;
CHyprColor hiddenBase;
int transitionMs = 0;
bool invertNum = false;
bool swapFont = false;
} colorConfig;
struct {
PHLANIMVAR<CGradientValueData> outer;
PHLANIMVAR<CHyprColor> inner;
// Font color is only chaned, when `swap_font_color` is set to true and no border is present.
// It is not animated, because that does not look good and we would need to rerender the text for each frame.
CHyprColor font;
} colorState;
bool fadeOnEmpty;
uint64_t fadeTimeoutMs;
CShadowable shadow;
};
0707010000005B000081A4000000000000000000000001688B5FE9000005E8000000000000000000000000000000000000003300000000hyprlock-0.9.1/src/renderer/widgets/Shadowable.cpp#include "Shadowable.hpp"
#include "../Renderer.hpp"
#include <hyprlang.hpp>
void CShadowable::configure(AWP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) {
m_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() {
const auto WIDGET = m_widget.lock();
if (!m_widget)
return;
if (passes == 0)
return;
if (!shadowFB.isAllocated())
shadowFB.alloc(viewport.x, viewport.y, true);
g_pRenderer->pushFb(shadowFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
ignoreDraw = true;
WIDGET->draw(IWidget::SRenderData{.opacity = 1.0});
ignoreDraw = false;
g_pRenderer->blurFB(shadowFB, CRenderer::SBlurParams{.size = size, .passes = passes, .colorize = color, .boostA = boostA});
g_pRenderer->popFb();
}
bool CShadowable::draw(const IWidget::SRenderData& data) {
if (!m_widget || passes == 0)
return true;
if (!shadowFB.isAllocated() || ignoreDraw)
return true;
CBox box = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, HYPRUTILS_TRANSFORM_NORMAL);
return true;
}
0707010000005C000081A4000000000000000000000001688B5FE9000003A8000000000000000000000000000000000000003300000000hyprlock-0.9.1/src/renderer/widgets/Shadowable.hpp#pragma once
#include "../Framebuffer.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "IWidget.hpp"
#include <string>
#include <unordered_map>
#include <any>
class CShadowable {
public:
virtual ~CShadowable() = default;
CShadowable() = default;
void configure(AWP<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:
AWP<IWidget> m_widget;
int size = 10;
int passes = 4;
float boostA = 1.0;
CHyprColor color{0, 0, 0, 1.0};
Vector2D viewport;
// to avoid recursive shadows
bool ignoreDraw = false;
CFramebuffer shadowFB;
};
0707010000005D000081A4000000000000000000000001688B5FE900001125000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Shape.cpp#include "Shape.hpp"
#include "../Renderer.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <sys/types.h>
void CShape::registerSelf(const ASP<CShape>& self) {
m_self = self;
}
void CShape::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
viewport = pOutput->getViewport();
shadow.configure(m_self, props, viewport);
try {
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
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"));
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(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"));
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CShape: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CShape: {}", e.what()); //
}
angle = angle * M_PI / 180.0;
const Vector2D VBORDER = {border, border};
const Vector2D REALSIZE = size + VBORDER * 2.0;
const Vector2D OFFSET = angle == 0 ? Vector2D{0.0, 0.0} : Vector2D{1.0, 1.0};
pos = posFromHVAlign(viewport, xray ? size : REALSIZE + OFFSET * 2.0, pos, halign, valign, xray ? 0 : angle);
if (xray) {
shapeBox = {pos, size};
borderBox = {pos - VBORDER, REALSIZE};
} else {
shapeBox = {OFFSET + VBORDER, size};
borderBox = {OFFSET, REALSIZE};
}
}
bool CShape::draw(const SRenderData& data) {
if (firstRender) {
firstRender = false;
shadow.markShadowDirty();
}
shadow.draw(data);
const auto MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0;
if (xray) {
if (border > 0) {
const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI));
g_pRenderer->renderBorder(borderBox, borderGrad, border, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND), data.opacity);
}
glEnable(GL_SCISSOR_TEST);
glScissor(shapeBox.x, shapeBox.y, shapeBox.width, shapeBox.height);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_SCISSOR_TEST);
return data.opacity < 1.0;
}
if (!shapeFB.isAllocated()) {
const int ROUND = roundingForBox(shapeBox, rounding);
const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border);
Debug::log(LOG, "round: {}, borderround: {}", ROUND, BORDERROUND);
shapeFB.alloc(borderBox.width + (borderBox.x * 2.0), borderBox.height + (borderBox.y * 2.0), true);
g_pRenderer->pushFb(shapeFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (border > 0)
g_pRenderer->renderBorder(borderBox, borderGrad, border, BORDERROUND, 1.0);
g_pRenderer->renderRect(shapeBox, color, ROUND);
g_pRenderer->popFb();
}
CTexture* tex = &shapeFB.m_cTex;
CBox texbox = {pos, tex->m_vSize};
texbox.round();
texbox.rot = angle;
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return data.opacity < 1.0;
}
CBox CShape::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size.y},
size,
};
}
void CShape::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CShape::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}
0707010000005E000081A4000000000000000000000001688B5FE90000051A000000000000000000000000000000000000002E00000000hyprlock-0.9.1/src/renderer/widgets/Shape.hpp#pragma once
#include "IWidget.hpp"
#include "../../helpers/Color.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "Shadowable.hpp"
#include <hyprutils/math/Box.hpp>
#include <string>
#include <unordered_map>
#include <any>
class CShape : public IWidget {
public:
CShape() = default;
virtual ~CShape() = default;
void registerSelf(const ASP<CShape>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
private:
AWP<CShape> m_self;
CFramebuffer shapeFB;
int rounding;
double border;
double angle;
CHyprColor color;
CGradientValueData borderGrad;
Vector2D size;
Vector2D pos;
CBox shapeBox;
CBox borderBox;
bool xray;
std::string halign, valign;
bool firstRender = true;
std::string onclickCommand;
Vector2D viewport;
CShadowable shadow;
};
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!741 blocks