mirror of
https://github.com/hyprwm/hyprlock.git
synced 2025-05-13 05:40:42 +01:00
Compare commits
153 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f7f0c9c6b0 | ||
![]() |
c12cf8e509 | ||
![]() |
0c5fd97d61 | ||
![]() |
fae1c4f6fe | ||
![]() |
e3bd47e177 | ||
![]() |
6c64630df8 | ||
![]() |
0e3e7206bc | ||
![]() |
867a71dd78 | ||
![]() |
82808290d9 | ||
![]() |
eb28a71756 | ||
![]() |
b3f1aa7580 | ||
![]() |
248dfb09f7 | ||
![]() |
656704aeb0 | ||
![]() |
d953296227 | ||
![]() |
71d35aa75f | ||
![]() |
8f73c39f07 | ||
![]() |
a8de918cc4 | ||
![]() |
6daab0517c | ||
![]() |
854235e1c8 | ||
![]() |
dd4c1d5034 | ||
![]() |
0b1f2a97ef | ||
![]() |
ce1eb7b5f9 | ||
![]() |
1ebbc35c55 | ||
![]() |
d9a1625315 | ||
![]() |
9e54d02590 | ||
![]() |
f883e669d1 | ||
![]() |
ee8ee1f9f7 | ||
![]() |
7ab3162d66 | ||
![]() |
9e82fe3547 | ||
![]() |
a13b6f0d1a | ||
![]() |
78ad1d46b5 | ||
![]() |
cb1c504b38 | ||
![]() |
9f37c1c8e9 | ||
![]() |
712ab62a42 | ||
![]() |
e588351d1d | ||
![]() |
2a6ec58366 | ||
![]() |
f6e4c1374e | ||
![]() |
c4b2175822 | ||
![]() |
82b63a6930 | ||
![]() |
a27585b383 | ||
![]() |
ad7600dca9 | ||
![]() |
dc6d72158c | ||
![]() |
ce750456f9 | ||
![]() |
ec82da7108 | ||
![]() |
465148ac21 | ||
![]() |
c976b6a1d1 | ||
![]() |
1bfa79eb83 | ||
![]() |
e77bc92b99 | ||
![]() |
408ce95dd0 | ||
![]() |
07b5e1b4cd | ||
![]() |
742eb98c6a | ||
![]() |
d547d1d4e3 | ||
![]() |
02639c2759 | ||
![]() |
4f964371cc | ||
![]() |
023aff52ad | ||
![]() |
e84267085d | ||
![]() |
a5e346783f | ||
![]() |
73e23e535f | ||
![]() |
de844d39ad | ||
![]() |
00d2cbfee3 | ||
![]() |
8f68fad50a | ||
![]() |
c3d95953c0 | ||
![]() |
e01afaf107 | ||
![]() |
a2f00fb735 | ||
![]() |
836dbfbb13 | ||
![]() |
2c5ae4d661 | ||
![]() |
3d63d9b129 | ||
![]() |
753c538dea | ||
![]() |
77d194c1e9 | ||
![]() |
d94cc3a5ab | ||
![]() |
820eaff7a8 | ||
![]() |
d212f4cc10 | ||
![]() |
181294c4d8 | ||
![]() |
90bc9764f1 | ||
![]() |
bf37645daa | ||
![]() |
5361dc40da | ||
![]() |
002e44f627 | ||
![]() |
83bda4883d | ||
![]() |
8a9f05fa1f | ||
![]() |
f2c153c09b | ||
![]() |
ee37e41723 | ||
![]() |
d12b4a7fba | ||
![]() |
058830668e | ||
![]() |
381a284b3b | ||
![]() |
fe35a8068c | ||
![]() |
61be0cb652 | ||
![]() |
a4b0562749 | ||
![]() |
4681f8f7f3 | ||
![]() |
8010b81e7b | ||
![]() |
cc7ffe73e7 | ||
![]() |
4667f721be | ||
![]() |
578246b996 | ||
![]() |
b9cf5151ba | ||
![]() |
d159e14e5b | ||
![]() |
2775ab2868 | ||
![]() |
c3c28feb4c | ||
![]() |
eadcbc0aed | ||
![]() |
d8bd25b52d | ||
![]() |
6c3c444136 | ||
![]() |
4fc133c96f | ||
![]() |
1cd3231537 | ||
![]() |
f225e23e5b | ||
![]() |
edbecc8708 | ||
![]() |
29dd33d6a4 | ||
![]() |
ae3bb0fd43 | ||
![]() |
f13d97e6d6 | ||
![]() |
a093a9eefd | ||
![]() |
f48540fcd4 | ||
![]() |
b808086286 | ||
![]() |
11694528b4 | ||
![]() |
5065788a47 | ||
![]() |
71021cc3de | ||
![]() |
7362ce3435 | ||
![]() |
264fb8b70f | ||
![]() |
eb63207ef0 | ||
![]() |
d9c2a5e0b7 | ||
![]() |
9ea804788c | ||
![]() |
153977aab3 | ||
![]() |
0b030d33c8 | ||
![]() |
73b0fc26c0 | ||
![]() |
cc71c0b7d9 | ||
![]() |
9c1e9e7db2 | ||
![]() |
a0af542f9b | ||
![]() |
7bb4113a7e | ||
![]() |
f673759d01 | ||
![]() |
9393a3e94d | ||
![]() |
8cffe0618c | ||
![]() |
5d85ea03b0 | ||
![]() |
c7fa5026c0 | ||
![]() |
8a89181e69 | ||
![]() |
5e8f12c2d9 | ||
![]() |
cf0e975fed | ||
![]() |
3e71799b30 | ||
![]() |
58e1a4a499 | ||
![]() |
dba9d8b517 | ||
![]() |
3d3b52e42c | ||
![]() |
20c01d91d4 | ||
![]() |
372a4cd55e | ||
![]() |
a29012cbbd | ||
![]() |
cac93f67e8 | ||
![]() |
9514925a7c | ||
![]() |
a3d8a2c128 | ||
![]() |
69d37d2663 | ||
![]() |
b407128cae | ||
![]() |
e5f0b56d07 | ||
![]() |
944caff79f | ||
![]() |
d8ccc6f96a | ||
![]() |
3bedae4436 | ||
![]() |
e72f601209 | ||
![]() |
43f2b7441b | ||
![]() |
0552a1eddd | ||
![]() |
a50296c181 | ||
![]() |
7fb3c03500 |
85 changed files with 5671 additions and 3205 deletions
101
.clang-tidy
Normal file
101
.clang-tidy
Normal file
|
@ -0,0 +1,101 @@
|
|||
WarningsAsErrors: '*'
|
||||
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
|
104
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
104
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
name: 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.
|
||||
|
19
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
name: 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
|
||||
|
1
.github/workflows/nix.yml
vendored
1
.github/workflows/nix.yml
vendored
|
@ -8,7 +8,6 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
# not needed (yet)
|
||||
# - uses: cachix/cachix-action@v12
|
||||
|
|
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -1,9 +1,14 @@
|
|||
.cache
|
||||
**/.cache
|
||||
.direnv
|
||||
.envrc
|
||||
.vscode/
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
build/
|
||||
compile_commands.json
|
||||
protocols/*.c
|
||||
protocols/*.h
|
||||
protocols/*.cpp
|
||||
protocols/*.hpp
|
||||
*.kdev4
|
||||
.gdb_history
|
||||
|
|
148
CMakeLists.txt
148
CMakeLists.txt
|
@ -1,11 +1,12 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
|
||||
set(VERSION 0.4.0)
|
||||
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
|
||||
string(STRIP ${VER_RAW} VERSION)
|
||||
|
||||
project(hyprlock
|
||||
project(
|
||||
hyprlock
|
||||
DESCRIPTION "A gpu-accelerated screen lock for Hyprland"
|
||||
VERSION ${VERSION}
|
||||
)
|
||||
VERSION ${VERSION})
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
|
@ -18,10 +19,7 @@ else()
|
|||
message(STATUS "Configuring hyprlock in Release with CMake")
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
.
|
||||
"protocols/"
|
||||
)
|
||||
include_directories(. "protocols/")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
@ -30,6 +28,40 @@ 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}/=)
|
||||
|
||||
|
@ -38,53 +70,83 @@ message(STATUS "Checking deps...")
|
|||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon libjpeg libwebp libmagic cairo pangocairo libdrm gbm hyprutils>=0.1.1)
|
||||
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
|
||||
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
|
||||
pkg_check_modules(
|
||||
deps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
wayland-client
|
||||
wayland-protocols>=1.35
|
||||
wayland-egl
|
||||
hyprlang>=0.6.0
|
||||
egl
|
||||
xkbcommon
|
||||
cairo
|
||||
pangocairo
|
||||
libdrm
|
||||
gbm
|
||||
hyprutils>=0.5.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::GL)
|
||||
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
|
||||
OpenGL::EGL OpenGL::GLES3)
|
||||
|
||||
# protocols
|
||||
find_program(WaylandScanner NAMES wayland-scanner)
|
||||
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
|
||||
execute_process(
|
||||
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
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(protocol protoPath protoName external)
|
||||
function(protocolnew protoPath protoName external)
|
||||
if(external)
|
||||
execute_process(
|
||||
COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
execute_process(
|
||||
COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
|
||||
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
execute_process(
|
||||
COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
|
||||
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
|
||||
protocol("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false)
|
||||
protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false)
|
||||
protocol("unstable/tablet/tablet-unstable-v2.xml" "tablet-unstable-v2" false)
|
||||
protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false)
|
||||
protocol("stable/viewporter/viewporter.xml" "viewporter" false)
|
||||
protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true)
|
||||
protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false)
|
||||
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}/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)
|
||||
|
|
44
README.md
44
README.md
|
@ -2,10 +2,14 @@
|
|||
Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility.
|
||||
|
||||
## Features
|
||||
- uses the secure ext-session-lock protocol
|
||||
- full support for fractional-scale
|
||||
- fully GPU accelerated
|
||||
- multi-threaded resource acquisition for no hitches
|
||||
- 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
|
||||
|
||||
|
@ -16,30 +20,32 @@ Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility
|
|||
|
||||
## Arch install
|
||||
```sh
|
||||
yay -S hyprlock-git
|
||||
pacman -S hyprlock # binary x86 tagged release
|
||||
# or
|
||||
yay -S hyprlock-git # compiles from latest source
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Deps
|
||||
You also need the following dependencies
|
||||
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
|
||||
- mesa
|
||||
|
||||
And the development libraries for the following
|
||||
- cairo
|
||||
- libdrm
|
||||
- pango
|
||||
- xkbcommon
|
||||
- pam
|
||||
- hyprlang >= 0.4
|
||||
- libmagic (file-devel on Fedora)
|
||||
|
||||
Development libraries are usually suffixed with `-devel` or `-dev` in most distro repos.
|
||||
|
||||
You also need to install `mesa-libgbm-devel` on some distros like RPM based ones where its not
|
||||
bundled with the mesa package.
|
||||
Sometimes distro packages are missing required development files.
|
||||
Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`.
|
||||
|
||||
### Building
|
||||
|
||||
|
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.8.2
|
106
assets/example.conf
Normal file
106
assets/example.conf
Normal file
|
@ -0,0 +1,106 @@
|
|||
# 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
|
||||
}
|
69
flake.lock
69
flake.lock
|
@ -1,5 +1,31 @@
|
|||
{
|
||||
"nodes": {
|
||||
"hyprgraphics": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
"hyprutils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743953322,
|
||||
"narHash": "sha256-prQ5JKopXtzCMX2eT3dXbaVvGmzjMRE2bXStQDdazpM=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "9d7f2687c84c729afbc3b13f7937655570f2978d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprlang": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
|
@ -13,11 +39,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1717881852,
|
||||
"narHash": "sha256-XeeVoKHQgfKuXoP6q90sUqKyl7EYy3ol2dVZGM+Jj94=",
|
||||
"lastModified": 1744468525,
|
||||
"narHash": "sha256-9HySx+EtsbbKlZDlY+naqqOV679VdxP6x6fP3wxDXJk=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "ec6938c66253429192274d612912649a0cfe4d28",
|
||||
"rev": "f1000c54d266e6e4e9d646df0774fac5b8a652df",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -36,11 +62,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1717881334,
|
||||
"narHash": "sha256-a0inRgJhPL6v9v7RPM/rx1kbXdfe3xJA1c9z0ZkYnh4=",
|
||||
"lastModified": 1743950287,
|
||||
"narHash": "sha256-/6IAEWyb8gC/NKZElxiHChkouiUOrVYNq9YqG0Pzm4Y=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "0693f9398ab693d89c9a0aa3b3d062dd61b7a60e",
|
||||
"rev": "f2dc70e448b994cef627a157ee340135bd68fbc6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -49,13 +75,36 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprwayland-scanner": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1739870480,
|
||||
"narHash": "sha256-SiDN5BGxa/1hAsqhgJsS03C3t2QrLgBT8u+ENJ0Qzwc=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"rev": "206367a08dc5ac4ba7ad31bdca391d098082e64b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1717602782,
|
||||
"narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=",
|
||||
"lastModified": 1744463964,
|
||||
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6",
|
||||
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -67,8 +116,10 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"hyprgraphics": "hyprgraphics",
|
||||
"hyprlang": "hyprlang",
|
||||
"hyprutils": "hyprutils",
|
||||
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"systems": "systems"
|
||||
}
|
||||
|
|
15
flake.nix
15
flake.nix
|
@ -5,6 +5,13 @@
|
|||
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";
|
||||
|
@ -17,6 +24,12 @@
|
|||
inputs.systems.follows = "systems";
|
||||
inputs.hyprutils.follows = "hyprutils";
|
||||
};
|
||||
|
||||
hyprwayland-scanner = {
|
||||
url = "github:hyprwm/hyprwayland-scanner";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
|
@ -33,7 +46,7 @@
|
|||
overlays = with self.overlays; [default];
|
||||
});
|
||||
in {
|
||||
overlays = import ./nix/overlays.nix {inherit inputs lib;};
|
||||
overlays = import ./nix/overlays.nix {inherit inputs lib self;};
|
||||
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprlock;
|
||||
|
|
|
@ -4,20 +4,23 @@
|
|||
cmake,
|
||||
pkg-config,
|
||||
cairo,
|
||||
file,
|
||||
libdrm,
|
||||
libGL,
|
||||
libjpeg,
|
||||
libwebp,
|
||||
libxkbcommon,
|
||||
mesa,
|
||||
libgbm,
|
||||
hyprgraphics,
|
||||
hyprlang,
|
||||
hyprutils,
|
||||
hyprwayland-scanner,
|
||||
pam,
|
||||
pango,
|
||||
sdbus-cpp,
|
||||
systemdLibs,
|
||||
wayland,
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
version ? "git",
|
||||
shortRev ? "",
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
pname = "hyprlock";
|
||||
|
@ -28,25 +31,32 @@ stdenv.mkDerivation {
|
|||
nativeBuildInputs = [
|
||||
cmake
|
||||
pkg-config
|
||||
hyprwayland-scanner
|
||||
wayland-scanner
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
cairo
|
||||
file
|
||||
libdrm
|
||||
libGL
|
||||
libjpeg
|
||||
libwebp
|
||||
libxkbcommon
|
||||
mesa
|
||||
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";
|
||||
|
|
|
@ -1,24 +1,44 @@
|
|||
{
|
||||
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.gcc13Stdenv;
|
||||
version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
|
||||
stdenv = prev.gcc14Stdenv;
|
||||
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=";
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
124
src/auth/Auth.cpp
Normal file
124
src/auth/Auth.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#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(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->unlock();
|
||||
}
|
||||
|
||||
void CAuth::enqueueUnlock() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), unlockCallback, nullptr);
|
||||
}
|
||||
|
||||
static void passwordFailCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pAuth->m_bDisplayFailText = true;
|
||||
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
|
||||
g_pHyprlock->renderAllOutputs();
|
||||
}
|
||||
|
||||
static void displayFailTimeoutCallback(std::shared_ptr<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();
|
||||
}
|
67
src/auth/Auth.hpp
Normal file
67
src/auth/Auth.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#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;
|
||||
std::shared_ptr<CTimer> m_resetDisplayFailTimer;
|
||||
};
|
||||
|
||||
inline UP<CAuth> g_pAuth;
|
270
src/auth/Fingerprint.cpp
Normal file
270
src/auth/Fingerprint.cpp
Normal file
|
@ -0,0 +1,270 @@
|
|||
#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), [](std::shared_ptr<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;
|
||||
}
|
53
src/auth/Fingerprint.hpp
Normal file
53
src/auth/Fingerprint.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#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();
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
#include "Auth.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "Pam.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "src/config/ConfigManager.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
|
@ -15,7 +15,7 @@
|
|||
#include <thread>
|
||||
|
||||
int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
|
||||
const auto CONVERSATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr;
|
||||
const auto CONVERSATIONSTATE = (CPam::SPamConversationState*)appdata_ptr;
|
||||
struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response));
|
||||
bool initialPrompt = true;
|
||||
|
||||
|
@ -32,20 +32,27 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
|
|||
|
||||
// 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) {
|
||||
if (!initialPrompt && PROMPTCHANGED) {
|
||||
CONVERSATIONSTATE->prompt = PROMPT;
|
||||
g_pAuth->waitForInput();
|
||||
CONVERSATIONSTATE->waitForInput();
|
||||
}
|
||||
|
||||
// Needed for unlocks via SIGUSR1
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
if (g_pHyprlock->isUnlocked())
|
||||
return PAM_CONV_ERR;
|
||||
|
||||
pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str());
|
||||
initialPrompt = false;
|
||||
} break;
|
||||
case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break;
|
||||
case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,38 +60,60 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
|
|||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
CAuth::CAuth() {
|
||||
static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module"));
|
||||
m_sPamModule = *PPAMMODULE;
|
||||
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, "Pam module \"/etc/pam.d/{}\" does not exist! Falling back to \"/etc/pam.d/su\"", 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(); };
|
||||
}
|
||||
|
||||
static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->onPasswordCheckTimer();
|
||||
CPam::~CPam() {
|
||||
;
|
||||
}
|
||||
|
||||
void CAuth::start() {
|
||||
std::thread([this]() {
|
||||
void CPam::init() {
|
||||
m_thread = std::thread([this]() {
|
||||
while (true) {
|
||||
resetConversation();
|
||||
auth();
|
||||
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||
}).detach();
|
||||
// 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 CAuth::auth() {
|
||||
const pam_conv localConv = {conv, (void*)&m_sConversationState};
|
||||
pam_handle_t* handle = NULL;
|
||||
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.success = false;
|
||||
m_sConversationState.failText = "pam_start failed";
|
||||
Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule);
|
||||
return false;
|
||||
|
@ -97,31 +126,19 @@ bool CAuth::auth() {
|
|||
m_sConversationState.waitingForPamAuth = false;
|
||||
|
||||
if (ret != PAM_SUCCESS) {
|
||||
m_sConversationState.success = false;
|
||||
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.success = true;
|
||||
m_sConversationState.failText = "Successfully authenticated";
|
||||
Debug::log(LOG, "auth: authenticated for {}", m_sPamModule);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAuth::didAuthSucceed() {
|
||||
return m_sConversationState.success;
|
||||
}
|
||||
|
||||
// clearing the input must be done from the main thread
|
||||
static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->clearPasswordBuffer();
|
||||
}
|
||||
|
||||
void CAuth::waitForInput() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr);
|
||||
|
||||
void CPam::waitForInput() {
|
||||
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
|
||||
m_bBlockInput = false;
|
||||
m_sConversationState.waitingForPamAuth = false;
|
||||
|
@ -130,7 +147,7 @@ void CAuth::waitForInput() {
|
|||
m_bBlockInput = true;
|
||||
}
|
||||
|
||||
void CAuth::submitInput(std::string input) {
|
||||
void CPam::handleInput(const std::string& input) {
|
||||
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
|
||||
|
||||
if (!m_sConversationState.inputRequested)
|
||||
|
@ -142,25 +159,27 @@ void CAuth::submitInput(std::string input) {
|
|||
m_sConversationState.inputSubmittedCondition.notify_all();
|
||||
}
|
||||
|
||||
std::optional<std::string> CAuth::getLastFailText() {
|
||||
std::optional<std::string> CPam::getLastFailText() {
|
||||
return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText);
|
||||
}
|
||||
|
||||
std::optional<std::string> CAuth::getLastPrompt() {
|
||||
std::optional<std::string> CPam::getLastPrompt() {
|
||||
return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt);
|
||||
}
|
||||
|
||||
bool CAuth::checkWaiting() {
|
||||
bool CPam::checkWaiting() {
|
||||
return m_bBlockInput || m_sConversationState.waitingForPamAuth;
|
||||
}
|
||||
|
||||
void CAuth::terminate() {
|
||||
void CPam::terminate() {
|
||||
m_sConversationState.inputSubmittedCondition.notify_all();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void CAuth::resetConversation() {
|
||||
void CPam::resetConversation() {
|
||||
m_sConversationState.input = "";
|
||||
m_sConversationState.waitingForPamAuth = false;
|
||||
m_sConversationState.inputRequested = false;
|
||||
m_sConversationState.success = false;
|
||||
m_sConversationState.failTextFromPam = false;
|
||||
}
|
53
src/auth/Pam.hpp
Normal file
53
src/auth/Pam.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#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();
|
||||
};
|
133
src/config/ConfigDataValues.hpp
Normal file
133
src/config/ConfigDataValues.hpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
#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;
|
||||
}
|
||||
};
|
|
@ -1,10 +1,23 @@
|
|||
#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;
|
||||
|
@ -17,22 +30,163 @@ static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::string getConfigDir() {
|
||||
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
|
||||
static Hyprlang::CParseResult handleBezier(const char* c, const char* v) {
|
||||
const std::string VALUE = v;
|
||||
const std::string COMMAND = c;
|
||||
|
||||
if (xdgConfigHome && std::filesystem::path(xdgConfigHome).is_absolute())
|
||||
return xdgConfigHome;
|
||||
const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);
|
||||
|
||||
static const char* home = getenv("HOME");
|
||||
Hyprlang::CParseResult result;
|
||||
if (RESULT.has_value())
|
||||
result.setError(RESULT.value().c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!home)
|
||||
throw std::runtime_error("Neither HOME nor XDG_CONFIG_HOME is set in the environment. Cannot determine config directory.");
|
||||
static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {
|
||||
const std::string VALUE = v;
|
||||
const std::string COMMAND = c;
|
||||
|
||||
return home + std::string("/.config");
|
||||
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() {
|
||||
return getConfigDir() + "/hypr/hyprlock.conf";
|
||||
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) :
|
||||
|
@ -40,6 +194,14 @@ CConfigManager::CConfigManager(std::string configPath) :
|
|||
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) \
|
||||
|
@ -48,14 +210,25 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
|
||||
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
|
||||
|
||||
m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0});
|
||||
#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:grace", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
|
||||
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{""});
|
||||
|
@ -69,21 +242,25 @@ void CConfigManager::init() {
|
|||
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", Hyprlang::VEC2{100, 100});
|
||||
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", Hyprlang::INT{0xFF00CFE6});
|
||||
m_config.addSpecialConfigValue("shape", "border_color", GRADIENTCONFIG("0xFF00CFE6"));
|
||||
m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111});
|
||||
m_config.addSpecialConfigValue("shape", "position", Hyprlang::VEC2{0, 80});
|
||||
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{""});
|
||||
|
@ -91,8 +268,8 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue("image", "size", Hyprlang::INT{150});
|
||||
m_config.addSpecialConfigValue("image", "rounding", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("image", "border_size", Hyprlang::INT{4});
|
||||
m_config.addSpecialConfigValue("image", "border_color", Hyprlang::INT{0xFFDDDDDD});
|
||||
m_config.addSpecialConfigValue("image", "position", Hyprlang::VEC2{0, 200});
|
||||
m_config.addSpecialConfigValue("image", "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});
|
||||
|
@ -100,33 +277,36 @@ void CConfigManager::init() {
|
|||
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", Hyprlang::VEC2{400, 90});
|
||||
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", Hyprlang::INT{0xFF111111});
|
||||
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", Hyprlang::VEC2{0, -20});
|
||||
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", Hyprlang::INT{0xFFCC8822});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_color", Hyprlang::INT{0xFFCC2222});
|
||||
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", "fail_transition", Hyprlang::INT{300});
|
||||
m_config.addSpecialConfigValue("input-field", "capslock_color", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("input-field", "numlock_color", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("input-field", "bothlock_color", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("input-field", "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});
|
||||
|
@ -134,19 +314,45 @@ void CConfigManager::init() {
|
|||
|
||||
m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
|
||||
m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""});
|
||||
m_config.addSpecialConfigValue("label", "position", Hyprlang::VEC2{400, 90});
|
||||
m_config.addSpecialConfigValue("label", "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{"none"});
|
||||
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"none"});
|
||||
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", {false});
|
||||
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();
|
||||
|
||||
|
@ -156,13 +362,7 @@ void CConfigManager::init() {
|
|||
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
|
||||
|
||||
#undef SHADOWABLE
|
||||
}
|
||||
|
||||
std::mutex configMtx;
|
||||
|
||||
void* const* CConfigManager::getValuePtr(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lg(configMtx);
|
||||
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
|
||||
#undef CLICKABLE
|
||||
}
|
||||
|
||||
std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
||||
|
@ -174,14 +374,17 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
"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{
|
||||
"background",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())),
|
||||
{
|
||||
.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())},
|
||||
|
@ -192,6 +395,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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
|
||||
|
@ -202,9 +408,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
for (auto& k : keys) {
|
||||
// clang-format off
|
||||
result.push_back(CConfigManager::SWidgetConfig{
|
||||
"shape",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())),
|
||||
{
|
||||
.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())},
|
||||
|
@ -217,6 +423,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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
|
||||
|
@ -227,9 +434,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
for (auto& k : keys) {
|
||||
// clang-format off
|
||||
result.push_back(CConfigManager::SWidgetConfig{
|
||||
"image",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("image", "monitor", k.c_str())),
|
||||
{
|
||||
.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())},
|
||||
|
@ -243,6 +450,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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
|
||||
|
@ -252,9 +460,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
for (auto& k : keys) {
|
||||
// clang-format off
|
||||
result.push_back(CConfigManager::SWidgetConfig{
|
||||
"input-field",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())),
|
||||
{
|
||||
.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())},
|
||||
|
@ -263,19 +471,21 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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())},
|
||||
{"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", k.c_str())},
|
||||
{"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())},
|
||||
{"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())},
|
||||
{"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())},
|
||||
|
@ -292,9 +502,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
for (auto& k : keys) {
|
||||
// clang-format off
|
||||
result.push_back(CConfigManager::SWidgetConfig{
|
||||
"label",
|
||||
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())),
|
||||
{
|
||||
.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())},
|
||||
|
@ -306,6 +516,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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
|
||||
|
@ -359,3 +570,80 @@ std::optional<std::string> CConfigManager::handleSource(const std::string& comma
|
|||
|
||||
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 {};
|
||||
}
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "../helpers/Log.hpp"
|
||||
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
class CConfigManager {
|
||||
public:
|
||||
CConfigManager(std::string configPath);
|
||||
void init();
|
||||
void* const* getValuePtr(const std::string& name);
|
||||
|
||||
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;
|
||||
|
@ -22,12 +27,17 @@ class CConfigManager {
|
|||
};
|
||||
|
||||
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 std::unique_ptr<CConfigManager> g_pConfigManager;
|
||||
inline UP<CConfigManager> g_pConfigManager;
|
||||
|
|
127
src/core/AnimationManager.cpp
Normal file
127
src/core/AnimationManager.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#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;
|
||||
}
|
33
src/core/AnimationManager.hpp
Normal file
33
src/core/AnimationManager.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#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;
|
|
@ -1,54 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
class CAuth {
|
||||
public:
|
||||
struct SPamConversationState {
|
||||
std::string input = "";
|
||||
std::string prompt = "";
|
||||
std::string failText = "";
|
||||
|
||||
std::mutex inputMutex;
|
||||
std::condition_variable inputSubmittedCondition;
|
||||
|
||||
bool waitingForPamAuth = false;
|
||||
bool inputRequested = false;
|
||||
|
||||
bool success = false;
|
||||
};
|
||||
|
||||
CAuth();
|
||||
|
||||
void start();
|
||||
bool auth();
|
||||
bool didAuthSucceed();
|
||||
|
||||
void waitForInput();
|
||||
void submitInput(std::string input);
|
||||
|
||||
std::optional<std::string> getLastFailText();
|
||||
std::optional<std::string> getLastPrompt();
|
||||
|
||||
bool checkWaiting();
|
||||
|
||||
void terminate();
|
||||
|
||||
// Should only be set via the main thread
|
||||
bool m_bDisplayFailText = false;
|
||||
|
||||
private:
|
||||
SPamConversationState m_sConversationState;
|
||||
|
||||
bool m_bBlockInput = true;
|
||||
|
||||
std::string m_sPamModule;
|
||||
|
||||
void resetConversation();
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CAuth> g_pAuth;
|
|
@ -1,20 +1,23 @@
|
|||
#include "CursorShape.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "Seat.hpp"
|
||||
|
||||
CCursorShape::CCursorShape(wp_cursor_shape_manager_v1* mgr) : mgr(mgr) {
|
||||
if (!g_pHyprlock->m_pPointer)
|
||||
CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
|
||||
if (!g_pSeatManager->m_pPointer)
|
||||
return;
|
||||
|
||||
dev = wp_cursor_shape_manager_v1_get_pointer(mgr, g_pHyprlock->m_pPointer);
|
||||
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
|
||||
}
|
||||
|
||||
void CCursorShape::setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape) {
|
||||
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
|
||||
if (!g_pSeatManager->m_pPointer)
|
||||
return;
|
||||
|
||||
if (!dev)
|
||||
return;
|
||||
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
|
||||
|
||||
wp_cursor_shape_device_v1_set_shape(dev, serial, shape);
|
||||
dev->sendSetShape(lastCursorSerial, shape);
|
||||
}
|
||||
|
||||
void CCursorShape::hideCursor(const uint32_t serial) {
|
||||
wl_pointer_set_cursor(g_pHyprlock->m_pPointer, serial, nullptr, 0, 0);
|
||||
void CCursorShape::hideCursor() {
|
||||
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "cursor-shape-v1-protocol.h"
|
||||
#include "../defines.hpp"
|
||||
#include "cursor-shape-v1.hpp"
|
||||
|
||||
class CCursorShape {
|
||||
public:
|
||||
CCursorShape(wp_cursor_shape_manager_v1* mgr);
|
||||
CCursorShape(SP<CCWpCursorShapeManagerV1> mgr);
|
||||
|
||||
void setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape);
|
||||
void hideCursor(const uint32_t serial);
|
||||
void setShape(const wpCursorShapeDeviceV1Shape shape);
|
||||
void hideCursor();
|
||||
|
||||
uint32_t lastCursorSerial = 0;
|
||||
|
||||
private:
|
||||
wp_cursor_shape_manager_v1* mgr = nullptr;
|
||||
wp_cursor_shape_device_v1* dev = nullptr;
|
||||
SP<CCWpCursorShapeManagerV1> mgr = nullptr;
|
||||
SP<CCWpCursorShapeDeviceV1> dev = nullptr;
|
||||
};
|
|
@ -31,21 +31,21 @@ CEGL::CEGL(wl_display* display) {
|
|||
throw std::runtime_error("EGL_EXT_platform_wayland not supported");
|
||||
|
||||
eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
if (eglGetPlatformDisplayEXT == NULL)
|
||||
if (eglGetPlatformDisplayEXT == nullptr)
|
||||
throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT");
|
||||
|
||||
eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
|
||||
if (eglCreatePlatformWindowSurfaceEXT == NULL)
|
||||
if (eglCreatePlatformWindowSurfaceEXT == nullptr)
|
||||
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
|
||||
|
||||
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, NULL);
|
||||
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, NULL, NULL) == EGL_FALSE) {
|
||||
if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) {
|
||||
Debug::log(CRIT, "Failed to initialize EGL");
|
||||
goto error;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <memory>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
class CEGL {
|
||||
public:
|
||||
CEGL(wl_display*);
|
||||
|
@ -20,4 +19,4 @@ class CEGL {
|
|||
void makeCurrent(EGLSurface surf);
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CEGL> g_pEGL;
|
||||
inline UP<CEGL> g_pEGL;
|
||||
|
|
|
@ -1,107 +1,95 @@
|
|||
#include "LockSurface.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "Egl.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/AnimationManager.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../renderer/Renderer.hpp"
|
||||
|
||||
static void handleConfigure(void* data, ext_session_lock_surface_v1* surf, uint32_t serial, uint32_t width, uint32_t height) {
|
||||
const auto PSURF = (CSessionLockSurface*)data;
|
||||
PSURF->configure({width, height}, serial);
|
||||
}
|
||||
|
||||
static const ext_session_lock_surface_v1_listener lockListener = {
|
||||
.configure = handleConfigure,
|
||||
};
|
||||
|
||||
static void handlePreferredScale(void* data, wp_fractional_scale_v1* wp_fractional_scale_v1, uint32_t scale) {
|
||||
const auto PSURF = (CSessionLockSurface*)data;
|
||||
PSURF->fractionalScale = scale / 120.0;
|
||||
Debug::log(LOG, "got fractional {}", PSURF->fractionalScale);
|
||||
|
||||
if (PSURF->readyForFrame)
|
||||
PSURF->onScaleUpdate();
|
||||
}
|
||||
|
||||
static const wp_fractional_scale_v1_listener fsListener = {
|
||||
.preferred_scale = handlePreferredScale,
|
||||
};
|
||||
|
||||
CSessionLockSurface::~CSessionLockSurface() {
|
||||
if (fractional) {
|
||||
wp_viewport_destroy(viewport);
|
||||
wp_fractional_scale_v1_destroy(fractional);
|
||||
}
|
||||
|
||||
if (eglWindow)
|
||||
wl_egl_window_destroy(eglWindow);
|
||||
|
||||
ext_session_lock_surface_v1_destroy(lockSurface);
|
||||
wl_surface_destroy(surface);
|
||||
if (frameCallback)
|
||||
wl_callback_destroy(frameCallback);
|
||||
}
|
||||
|
||||
CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
|
||||
surface = wl_compositor_create_surface(g_pHyprlock->getCompositor());
|
||||
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");
|
||||
|
||||
if (!surface) {
|
||||
Debug::log(CRIT, "Couldn't create wl_surface");
|
||||
exit(1);
|
||||
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()));
|
||||
}
|
||||
|
||||
fractional = wp_fractional_scale_manager_v1_get_fractional_scale(g_pHyprlock->getFractionalMgr(), surface);
|
||||
if (fractional) {
|
||||
wp_fractional_scale_v1_add_listener(fractional, &fsListener, this);
|
||||
viewport = wp_viewporter_get_viewport(g_pHyprlock->getViewporter(), surface);
|
||||
} else
|
||||
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 = ext_session_lock_v1_get_lock_surface(g_pHyprlock->getSessionLock(), surface, output->output);
|
||||
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource()));
|
||||
RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1");
|
||||
|
||||
if (!surface) {
|
||||
Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ext_session_lock_surface_v1_add_listener(lockSurface, &lockListener, this);
|
||||
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 SAMESERIAL = serial == serial_;
|
||||
const bool SAMESIZE = logicalSize == size_;
|
||||
const bool SAMESCALE = appliedScale == fractionalScale;
|
||||
|
||||
const auto POUTPUT = m_outputRef.lock();
|
||||
|
||||
serial = serial_;
|
||||
size = (size_ * fractionalScale).floor();
|
||||
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);
|
||||
|
||||
if (!sameSerial)
|
||||
ext_session_lock_surface_v1_ack_configure(lockSurface, serial);
|
||||
|
||||
if (fractional)
|
||||
wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y);
|
||||
|
||||
wl_surface_set_buffer_scale(surface, 1);
|
||||
wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF);
|
||||
|
||||
if (!eglWindow)
|
||||
eglWindow = wl_egl_window_create(surface, size.x, size.y);
|
||||
else
|
||||
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
|
||||
surface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
|
||||
|
||||
if (!eglWindow) {
|
||||
Debug::log(CRIT, "Couldn't create eglWindow");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!eglSurface)
|
||||
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
|
||||
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) {
|
||||
Debug::log(CRIT, "Couldn't create eglSurface: {}", (int)glGetError());
|
||||
exit(1);
|
||||
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;
|
||||
|
@ -113,40 +101,45 @@ void CSessionLockSurface::onScaleUpdate() {
|
|||
configure(logicalSize, serial);
|
||||
}
|
||||
|
||||
static void handleDone(void* data, wl_callback* wl_callback, uint32_t callback_data) {
|
||||
const auto PSURF = (CSessionLockSurface*)data;
|
||||
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
return;
|
||||
|
||||
PSURF->onCallback();
|
||||
}
|
||||
|
||||
static const wl_callback_listener callbackListener = {
|
||||
.done = handleDone,
|
||||
};
|
||||
|
||||
void CSessionLockSurface::render() {
|
||||
Debug::log(TRACE, "render lock");
|
||||
|
||||
if (frameCallback || !readyForFrame) {
|
||||
needsFrame = true;
|
||||
return;
|
||||
}
|
||||
|
||||
g_pAnimationManager->tick();
|
||||
const auto FEEDBACK = g_pRenderer->renderLock(*this);
|
||||
frameCallback = wl_surface_frame(surface);
|
||||
wl_callback_add_listener(frameCallback, &callbackListener, 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;
|
||||
needsFrame = FEEDBACK.needsFrame || g_pAnimationManager->shouldTickForNext();
|
||||
}
|
||||
|
||||
void CSessionLockSurface::onCallback() {
|
||||
wl_callback_destroy(frameCallback);
|
||||
frameCallback = nullptr;
|
||||
frameCallback.reset();
|
||||
|
||||
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL)
|
||||
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL) {
|
||||
needsFrame = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
|
||||
return surface;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "ext-session-lock-v1-protocol.h"
|
||||
#include "viewporter-protocol.h"
|
||||
#include "fractional-scale-v1-protocol.h"
|
||||
#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 "../helpers/Vector2D.hpp"
|
||||
#include <EGL/egl.h>
|
||||
|
||||
class COutput;
|
||||
|
@ -13,7 +14,7 @@ class CRenderer;
|
|||
|
||||
class CSessionLockSurface {
|
||||
public:
|
||||
CSessionLockSurface(COutput* output);
|
||||
CSessionLockSurface(const SP<COutput>& pOutput);
|
||||
~CSessionLockSurface();
|
||||
|
||||
void configure(const Vector2D& size, uint32_t serial);
|
||||
|
@ -25,23 +26,31 @@ class CSessionLockSurface {
|
|||
void render();
|
||||
void onCallback();
|
||||
void onScaleUpdate();
|
||||
SP<CCWlSurface> getWlSurface();
|
||||
|
||||
private:
|
||||
COutput* output = nullptr;
|
||||
wl_surface* surface = nullptr;
|
||||
ext_session_lock_surface_v1* lockSurface = nullptr;
|
||||
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;
|
||||
wp_fractional_scale_v1* fractional = nullptr;
|
||||
wp_viewport* viewport = nullptr;
|
||||
SP<CCWpFractionalScaleV1> fractional = nullptr;
|
||||
SP<CCWpViewport> viewport = nullptr;
|
||||
|
||||
bool needsFrame = false;
|
||||
|
||||
uint32_t m_lastFrameTime = 0;
|
||||
uint32_t m_frames = 0;
|
||||
|
||||
// wayland callbacks
|
||||
wl_callback* frameCallback = nullptr;
|
||||
SP<CCWlCallback> frameCallback = nullptr;
|
||||
|
||||
friend class CRenderer;
|
||||
friend class COutput;
|
||||
};
|
|
@ -1,63 +1,69 @@
|
|||
#include "Output.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "../renderer/Renderer.hpp"
|
||||
|
||||
static void handleGeometry(void* data, wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make,
|
||||
const char* model, int32_t transform) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
POUTPUT->transform = (wl_output_transform)transform;
|
||||
void COutput::create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t _name) {
|
||||
m_ID = _name;
|
||||
m_wlOutput = pWlOutput;
|
||||
m_self = pSelf;
|
||||
|
||||
Debug::log(LOG, "output {} make {} model {}", POUTPUT->name, make ? make : "", model ? model : "");
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
static void handleMode(void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
|
||||
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 (POUTPUT->transform % 2 == 1)
|
||||
POUTPUT->size = {height, width};
|
||||
if (transform % 2 == 1)
|
||||
size = {height, width};
|
||||
else
|
||||
POUTPUT->size = {width, height};
|
||||
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 : "");
|
||||
});
|
||||
}
|
||||
|
||||
static void handleDone(void* data, wl_output* output) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
Debug::log(LOG, "output {} done", POUTPUT->name);
|
||||
if (g_pHyprlock->m_bLocked && !POUTPUT->sessionLockSurface) {
|
||||
// if we are already locked, create a surface dynamically
|
||||
Debug::log(LOG, "Creating a surface dynamically for output as we are already locked");
|
||||
POUTPUT->sessionLockSurface = std::make_unique<CSessionLockSurface>(POUTPUT);
|
||||
}
|
||||
void COutput::createSessionLockSurface() {
|
||||
if (!m_self.valid()) {
|
||||
Debug::log(ERR, "output {} dead??", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
static void handleScale(void* data, wl_output* output, int32_t factor) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
POUTPUT->scale = factor;
|
||||
if (m_sessionLockSurface) {
|
||||
Debug::log(ERR, "output {} already has a session lock surface", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
static void handleName(void* data, wl_output* output, const char* name) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
POUTPUT->stringName = std::string{name} + POUTPUT->stringName;
|
||||
POUTPUT->stringPort = std::string{name};
|
||||
Debug::log(LOG, "output {} name {}", POUTPUT->name, name);
|
||||
if (size == Vector2D{0, 0}) {
|
||||
Debug::log(WARN, "output {} refusing to create a lock surface with size 0x0", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
static void handleDescription(void* data, wl_output* output, const char* description) {
|
||||
const auto POUTPUT = (COutput*)data;
|
||||
POUTPUT->stringDesc = description ? std::string{description} : "";
|
||||
Debug::log(LOG, "output {} description {}", POUTPUT->name, POUTPUT->stringDesc);
|
||||
m_sessionLockSurface = makeUnique<CSessionLockSurface>(m_self.lock());
|
||||
}
|
||||
|
||||
static const wl_output_listener outputListener = {
|
||||
.geometry = handleGeometry,
|
||||
.mode = handleMode,
|
||||
.done = handleDone,
|
||||
.scale = handleScale,
|
||||
.name = handleName,
|
||||
.description = handleDescription,
|
||||
};
|
||||
|
||||
COutput::COutput(wl_output* output, uint32_t name) : name(name), output(output) {
|
||||
wl_output_add_listener(output, &outputListener, this);
|
||||
Vector2D COutput::getViewport() const {
|
||||
return (m_sessionLockSurface) ? m_sessionLockSurface->size : size;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "../helpers/Vector2D.hpp"
|
||||
#include "../defines.hpp"
|
||||
#include "wayland.hpp"
|
||||
#include "LockSurface.hpp"
|
||||
#include <memory>
|
||||
|
||||
class COutput {
|
||||
public:
|
||||
COutput(wl_output* output, uint32_t name);
|
||||
COutput() = default;
|
||||
~COutput() = default;
|
||||
|
||||
uint32_t name = 0;
|
||||
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;
|
||||
|
@ -18,9 +21,13 @@ class COutput {
|
|||
std::string stringPort = "";
|
||||
std::string stringDesc = "";
|
||||
|
||||
std::unique_ptr<CSessionLockSurface> sessionLockSurface;
|
||||
UP<CSessionLockSurface> m_sessionLockSurface;
|
||||
|
||||
wl_output* output = nullptr;
|
||||
SP<CCWlOutput> m_wlOutput = nullptr;
|
||||
|
||||
private:
|
||||
WP<COutput> m_self;
|
||||
|
||||
void createSessionLockSurface();
|
||||
|
||||
Vector2D getViewport() const;
|
||||
};
|
||||
|
|
163
src/core/Seat.cpp
Normal file
163
src/core/Seat.cpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
#include "Seat.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include <chrono>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
CSeatManager::~CSeatManager() {
|
||||
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_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;
|
||||
}
|
32
src/core/Seat.hpp
Normal file
32
src/core/Seat.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#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;
|
||||
|
||||
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>();
|
|
@ -1,8 +1,8 @@
|
|||
#include "Timer.hpp"
|
||||
|
||||
CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force) : cb(cb_), data(data_) {
|
||||
CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force) :
|
||||
cb(cb_), data(data_), allowForceUpdate(force) {
|
||||
expires = std::chrono::system_clock::now() + timeout;
|
||||
allowForceUpdate = force;
|
||||
}
|
||||
|
||||
bool CTimer::passed() {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "ext-session-lock-v1-protocol.h"
|
||||
#include "fractional-scale-v1-protocol.h"
|
||||
#include "linux-dmabuf-unstable-v1-protocol.h"
|
||||
#include "wlr-screencopy-unstable-v1-protocol.h"
|
||||
#include "viewporter-protocol.h"
|
||||
#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 "Auth.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>
|
||||
|
@ -28,15 +29,13 @@ struct SDMABUFModifier {
|
|||
|
||||
class CHyprlock {
|
||||
public:
|
||||
CHyprlock(const std::string& wlDisplay, const bool immediate);
|
||||
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender);
|
||||
~CHyprlock();
|
||||
|
||||
void run();
|
||||
|
||||
void unlock();
|
||||
|
||||
void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
|
||||
void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name);
|
||||
bool isUnlocked();
|
||||
|
||||
std::shared_ptr<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data,
|
||||
bool force = false);
|
||||
|
@ -46,18 +45,15 @@ class CHyprlock {
|
|||
void onLockLocked();
|
||||
void onLockFinished();
|
||||
|
||||
void acquireSessionLock();
|
||||
bool acquireSessionLock();
|
||||
void releaseSessionLock();
|
||||
|
||||
void attemptRestoreOnDeath();
|
||||
|
||||
void spawnAsync(const std::string& cmd);
|
||||
std::string spawnSync(const std::string& cmd);
|
||||
|
||||
void onKey(uint32_t key, bool down);
|
||||
void handleKeySym(xkb_keysym_t sym);
|
||||
void 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();
|
||||
|
@ -68,24 +64,15 @@ class CHyprlock {
|
|||
|
||||
size_t getPasswordBufferLen();
|
||||
size_t getPasswordBufferDisplayLen();
|
||||
size_t getPasswordFailedAttempts();
|
||||
|
||||
ext_session_lock_manager_v1* getSessionLockMgr();
|
||||
ext_session_lock_v1* getSessionLock();
|
||||
wl_compositor* getCompositor();
|
||||
SP<CCExtSessionLockManagerV1> getSessionLockMgr();
|
||||
SP<CCExtSessionLockV1> getSessionLock();
|
||||
SP<CCWlCompositor> getCompositor();
|
||||
wl_display* getDisplay();
|
||||
wp_fractional_scale_manager_v1* getFractionalMgr();
|
||||
wp_viewporter* getViewporter();
|
||||
zwlr_screencopy_manager_v1* getScreencopy();
|
||||
|
||||
wl_pointer* m_pPointer = nullptr;
|
||||
wl_keyboard* m_pKeeb = nullptr;
|
||||
|
||||
std::unique_ptr<CCursorShape> m_pCursorShape;
|
||||
|
||||
xkb_context* m_pXKBContext = nullptr;
|
||||
xkb_keymap* m_pXKBKeymap = nullptr;
|
||||
xkb_state* m_pXKBState = nullptr;
|
||||
SP<CCWpFractionalScaleManagerV1> getFractionalMgr();
|
||||
SP<CCWpViewporter> getViewporter();
|
||||
SP<CCZwlrScreencopyManagerV1> getScreencopy();
|
||||
SP<CCWlShm> getShm();
|
||||
|
||||
int32_t m_iKeebRepeatRate = 25;
|
||||
int32_t m_iKeebRepeatDelay = 600;
|
||||
|
@ -94,25 +81,32 @@ class CHyprlock {
|
|||
|
||||
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_bFadeStarted = false;
|
||||
|
||||
bool m_bImmediateRender = false;
|
||||
|
||||
std::string m_sCurrentDesktop = "";
|
||||
|
||||
//
|
||||
std::chrono::system_clock::time_point m_tGraceEnds;
|
||||
std::chrono::system_clock::time_point m_tFadeEnds;
|
||||
Vector2D m_vLastEnterCoords = {};
|
||||
WP<COutput> m_focusedOutput;
|
||||
|
||||
Vector2D m_vMouseLocation = {};
|
||||
|
||||
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<COutput>> m_vOutputs;
|
||||
std::vector<SP<COutput>> m_vOutputs;
|
||||
std::vector<std::shared_ptr<CTimer>> getTimers();
|
||||
|
||||
struct {
|
||||
void* linuxDmabuf = nullptr;
|
||||
void* linuxDmabufFeedback = nullptr;
|
||||
SP<CCZwpLinuxDmabufV1> linuxDmabuf = nullptr;
|
||||
SP<CCZwpLinuxDmabufFeedbackV1> linuxDmabufFeedback = nullptr;
|
||||
|
||||
gbm_bo* gbm = nullptr;
|
||||
gbm_device* gbmDevice = nullptr;
|
||||
|
@ -128,17 +122,19 @@ class CHyprlock {
|
|||
private:
|
||||
struct {
|
||||
wl_display* display = nullptr;
|
||||
wl_registry* registry = nullptr;
|
||||
wl_seat* seat = nullptr;
|
||||
ext_session_lock_manager_v1* sessionLock = nullptr;
|
||||
wl_compositor* compositor = nullptr;
|
||||
wp_fractional_scale_manager_v1* fractional = nullptr;
|
||||
wp_viewporter* viewporter = nullptr;
|
||||
zwlr_screencopy_manager_v1* screencopy = nullptr;
|
||||
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 {
|
||||
ext_session_lock_v1* lock = nullptr;
|
||||
SP<CCExtSessionLockV1> lock = nullptr;
|
||||
} m_sLockState;
|
||||
|
||||
struct {
|
||||
|
@ -154,14 +150,19 @@ class CHyprlock {
|
|||
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;
|
||||
|
||||
bool m_bUnlockedCalled = false;
|
||||
|
||||
std::vector<std::shared_ptr<CTimer>> m_vTimers;
|
||||
|
||||
std::vector<uint32_t> m_vPressedKeys;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHyprlock> g_pHyprlock;
|
||||
inline UP<CHyprlock> g_pHyprlock;
|
||||
|
|
14
src/defines.hpp
Normal file
14
src/defines.hpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <hyprgraphics/color/Color.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
using namespace Hyprgraphics;
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
|
||||
typedef int64_t OUTPUTID;
|
||||
constexpr OUTPUTID OUTPUT_INVALID = -1;
|
67
src/helpers/AnimatedVariable.hpp
Normal file
67
src/helpers/AnimatedVariable.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#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>>;
|
|
@ -1,101 +0,0 @@
|
|||
#include "Box.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
#define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x <= (x2) && (vec).y >= (y1) && (vec).y <= (y2))
|
||||
|
||||
CBox& CBox::scale(double scale) {
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
w *= scale;
|
||||
h *= scale;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox& CBox::scale(const Vector2D& scale) {
|
||||
x *= scale.x;
|
||||
y *= scale.y;
|
||||
w *= scale.x;
|
||||
h *= scale.y;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox& CBox::translate(const Vector2D& vec) {
|
||||
x += vec.x;
|
||||
y += vec.y;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2D CBox::middle() const {
|
||||
return Vector2D{x + w / 2.0, y + h / 2.0};
|
||||
}
|
||||
|
||||
bool CBox::containsPoint(const Vector2D& vec) const {
|
||||
return VECINRECT(vec, x, y, x + w, y + h);
|
||||
}
|
||||
|
||||
bool CBox::empty() const {
|
||||
return w == 0 || h == 0;
|
||||
}
|
||||
|
||||
CBox& CBox::round() {
|
||||
float newW = x + w - std::round(x);
|
||||
float newH = y + h - std::round(y);
|
||||
x = std::round(x);
|
||||
y = std::round(y);
|
||||
w = std::round(newW);
|
||||
h = std::round(newH);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox& CBox::scaleFromCenter(double scale) {
|
||||
double oldW = w, oldH = h;
|
||||
|
||||
w *= scale;
|
||||
h *= scale;
|
||||
|
||||
x -= (w - oldW) / 2.0;
|
||||
y -= (h - oldH) / 2.0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox& CBox::expand(const double& value) {
|
||||
x -= value;
|
||||
y -= value;
|
||||
w += value * 2.0;
|
||||
h += value * 2.0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox& CBox::noNegativeSize() {
|
||||
std::clamp(w, 0.0, std::numeric_limits<double>::infinity());
|
||||
std::clamp(h, 0.0, std::numeric_limits<double>::infinity());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBox CBox::roundInternal() {
|
||||
float newW = x + w - std::floor(x);
|
||||
float newH = y + h - std::floor(y);
|
||||
|
||||
return CBox{std::floor(x), std::floor(y), std::floor(newW), std::floor(newH)};
|
||||
}
|
||||
|
||||
CBox CBox::copy() const {
|
||||
return CBox{*this};
|
||||
}
|
||||
|
||||
Vector2D CBox::pos() const {
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
Vector2D CBox::size() const {
|
||||
return {w, h};
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Vector2D.hpp"
|
||||
|
||||
class CBox {
|
||||
public:
|
||||
CBox(double x_, double y_, double w_, double h_) {
|
||||
x = x_;
|
||||
y = y_;
|
||||
w = w_;
|
||||
h = h_;
|
||||
}
|
||||
|
||||
CBox() {
|
||||
w = 0;
|
||||
h = 0;
|
||||
}
|
||||
|
||||
CBox(const double d) {
|
||||
x = d;
|
||||
y = d;
|
||||
w = d;
|
||||
h = d;
|
||||
}
|
||||
|
||||
CBox(const Vector2D& pos, const Vector2D& size) {
|
||||
x = pos.x;
|
||||
y = pos.y;
|
||||
w = size.x;
|
||||
h = size.y;
|
||||
}
|
||||
|
||||
CBox& scale(double scale);
|
||||
CBox& scaleFromCenter(double scale);
|
||||
CBox& scale(const Vector2D& scale);
|
||||
CBox& translate(const Vector2D& vec);
|
||||
CBox& round();
|
||||
CBox& expand(const double& value);
|
||||
CBox& noNegativeSize();
|
||||
|
||||
CBox copy() const;
|
||||
|
||||
Vector2D middle() const;
|
||||
Vector2D pos() const;
|
||||
Vector2D size() const;
|
||||
|
||||
bool containsPoint(const Vector2D& vec) const;
|
||||
bool empty() const;
|
||||
|
||||
double x = 0, y = 0;
|
||||
union {
|
||||
double w;
|
||||
double width;
|
||||
};
|
||||
union {
|
||||
double h;
|
||||
double height;
|
||||
};
|
||||
|
||||
double rot = 0; /* rad, ccw */
|
||||
|
||||
//
|
||||
bool operator==(const CBox& rhs) const {
|
||||
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
|
||||
}
|
||||
|
||||
private:
|
||||
CBox roundInternal();
|
||||
};
|
|
@ -5,22 +5,43 @@
|
|||
#define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0)
|
||||
#define BLUE(c) ((double)(((c)) & 0xff) / 255.0)
|
||||
|
||||
CColor::CColor() {}
|
||||
|
||||
CColor::CColor(float r, float g, float b, float a) {
|
||||
this->r = r;
|
||||
this->g = g;
|
||||
this->b = b;
|
||||
this->a = a;
|
||||
CHyprColor::CHyprColor() {
|
||||
;
|
||||
}
|
||||
|
||||
CColor::CColor(uint64_t hex) {
|
||||
this->r = RED(hex);
|
||||
this->g = GREEN(hex);
|
||||
this->b = BLUE(hex);
|
||||
this->a = ALPHA(hex);
|
||||
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();
|
||||
}
|
||||
|
||||
uint64_t CColor::getAsHex() {
|
||||
return ((int)a) * 0x1000000 + ((int)r) * 0x10000 + ((int)g) * 0x100 + ((int)b) * 0x1;
|
||||
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};
|
||||
}
|
|
@ -1,34 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "../helpers/Log.hpp"
|
||||
#include <hyprgraphics/color/Color.hpp>
|
||||
|
||||
class CColor {
|
||||
class CHyprColor {
|
||||
public:
|
||||
CColor();
|
||||
CColor(float r, float g, float b, float a);
|
||||
CColor(uint64_t);
|
||||
CHyprColor();
|
||||
CHyprColor(float r, float g, float b, float a);
|
||||
CHyprColor(const Hyprgraphics::CColor& col, float a);
|
||||
CHyprColor(uint64_t);
|
||||
|
||||
float r = 0, g = 0, b = 0, a = 1.f;
|
||||
// AR32
|
||||
uint32_t getAsHex() const;
|
||||
Hyprgraphics::CColor::SSRGB asRGB() const;
|
||||
Hyprgraphics::CColor::SOkLab asOkLab() const;
|
||||
Hyprgraphics::CColor::SHSL asHSL() const;
|
||||
CHyprColor stripA() const;
|
||||
|
||||
uint64_t getAsHex();
|
||||
|
||||
CColor operator-(const CColor& c2) const {
|
||||
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
|
||||
//
|
||||
bool operator==(const CHyprColor& c2) const {
|
||||
return c2.r == r && c2.g == g && c2.b == b && c2.a == a;
|
||||
}
|
||||
|
||||
CColor operator+(const CColor& c2) const {
|
||||
return CColor(r + c2.r, g + c2.g, b + c2.b, a + c2.a);
|
||||
// stubs for the AnimationMgr
|
||||
CHyprColor operator-(const CHyprColor& c2) const {
|
||||
RASSERT(false, "CHyprColor: - is a STUB");
|
||||
return {};
|
||||
}
|
||||
|
||||
CColor operator*(const float& v) const {
|
||||
return CColor(r * v, g * v, b * v, a * v);
|
||||
CHyprColor operator+(const CHyprColor& c2) const {
|
||||
RASSERT(false, "CHyprColor: + is a STUB");
|
||||
return {};
|
||||
}
|
||||
|
||||
bool operator==(const CColor& c2) const {
|
||||
return r == c2.r && g == c2.g && b == c2.b && a == c2.a;
|
||||
CHyprColor operator*(const float& c2) const {
|
||||
RASSERT(false, "CHyprColor: * is a STUB");
|
||||
return {};
|
||||
}
|
||||
|
||||
CColor stripA() const {
|
||||
return {r, g, b, 1};
|
||||
}
|
||||
double r = 0, g = 0, b = 0, a = 0;
|
||||
|
||||
private:
|
||||
Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation
|
||||
};
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#include "Jpeg.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::filesystem::path& path) {
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) {
|
||||
Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* imageRawData;
|
||||
struct stat fileInfo = {};
|
||||
const auto FD = open(path.c_str(), O_RDONLY);
|
||||
|
||||
fstat(FD, &fileInfo);
|
||||
|
||||
imageRawData = malloc(fileInfo.st_size);
|
||||
|
||||
read(FD, imageRawData, fileInfo.st_size);
|
||||
close(FD);
|
||||
|
||||
// now the JPEG is in the memory
|
||||
|
||||
jpeg_decompress_struct decompressStruct = {};
|
||||
jpeg_error_mgr errorManager = {};
|
||||
|
||||
decompressStruct.err = jpeg_std_error(&errorManager);
|
||||
jpeg_create_decompress(&decompressStruct);
|
||||
jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size);
|
||||
jpeg_read_header(&decompressStruct, true);
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
decompressStruct.out_color_space = JCS_EXT_BGRA;
|
||||
#else
|
||||
decompressStruct.out_color_space = JCS_EXT_ARGB;
|
||||
#endif
|
||||
|
||||
// decompress
|
||||
jpeg_start_decompress(&decompressStruct);
|
||||
|
||||
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height);
|
||||
|
||||
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
|
||||
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
|
||||
JSAMPROW rowRead;
|
||||
|
||||
while (decompressStruct.output_scanline < decompressStruct.output_height) {
|
||||
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
|
||||
rowRead = PROW;
|
||||
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
|
||||
}
|
||||
|
||||
cairo_surface_flush(cairoSurface);
|
||||
cairo_surface_mark_dirty(cairoSurface);
|
||||
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
|
||||
jpeg_finish_decompress(&decompressStruct);
|
||||
jpeg_destroy_decompress(&decompressStruct);
|
||||
|
||||
return cairoSurface;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
namespace JPEG {
|
||||
cairo_surface_t* createSurfaceFromJPEG(const std::filesystem::path&);
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <print>
|
||||
|
||||
enum eLogLevel {
|
||||
TRACE = 0,
|
||||
|
@ -18,13 +18,23 @@ enum eLogLevel {
|
|||
Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \
|
||||
std::format(reason, ##__VA_ARGS__), __LINE__, \
|
||||
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
|
||||
printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \
|
||||
*((int*)nullptr) = 1; /* so that we crash and get a coredump */ \
|
||||
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;
|
||||
|
||||
|
@ -38,21 +48,7 @@ namespace Debug {
|
|||
return;
|
||||
|
||||
if (level != NONE) {
|
||||
std::cout << '[';
|
||||
|
||||
switch (level) {
|
||||
case TRACE: std::cout << "TRACE"; break;
|
||||
case INFO: std::cout << "INFO"; break;
|
||||
case LOG: std::cout << "LOG"; break;
|
||||
case WARN: std::cout << "WARN"; break;
|
||||
case ERR: std::cout << "ERR"; break;
|
||||
case CRIT: std::cout << "CRITICAL"; break;
|
||||
default: break;
|
||||
std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
std::cout << "] ";
|
||||
}
|
||||
|
||||
std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n";
|
||||
}
|
||||
};
|
23
src/helpers/Math.cpp
Normal file
23
src/helpers/Math.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#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;
|
||||
}
|
12
src/helpers/Math.hpp
Normal file
12
src/helpers/Math.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#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);
|
|
@ -1,5 +1,15 @@
|
|||
#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);
|
||||
|
@ -7,12 +17,144 @@ std::string absolutePath(const std::string& rawpath, const std::string& currentD
|
|||
// Handling where rawpath starts with '~'
|
||||
if (!rawpath.empty() && rawpath[0] == '~') {
|
||||
static const char* const ENVHOME = getenv("HOME");
|
||||
return std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2);
|
||||
path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2);
|
||||
}
|
||||
|
||||
// Handling e.g. ./, ../
|
||||
else if (path.is_relative()) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#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);
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
#include "Vector2D.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
Vector2D::Vector2D(double xx, double yy) {
|
||||
x = xx;
|
||||
y = yy;
|
||||
}
|
||||
|
||||
Vector2D::Vector2D() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
Vector2D::~Vector2D() {}
|
||||
|
||||
double Vector2D::normalize() {
|
||||
// get max abs
|
||||
const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y);
|
||||
|
||||
x /= max;
|
||||
y /= max;
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
Vector2D Vector2D::floor() const {
|
||||
return Vector2D(std::floor(x), std::floor(y));
|
||||
}
|
||||
|
||||
Vector2D Vector2D::round() const {
|
||||
return Vector2D(std::round(x), std::round(y));
|
||||
}
|
||||
|
||||
Vector2D Vector2D::clamp(const Vector2D& min, const Vector2D& max) const {
|
||||
return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y));
|
||||
}
|
||||
|
||||
double Vector2D::distance(const Vector2D& other) const {
|
||||
double dx = x - other.x;
|
||||
double dy = y - other.y;
|
||||
return std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
double Vector2D::size() const {
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
Vector2D Vector2D::getComponentMax(const Vector2D& other) const {
|
||||
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
|
||||
}
|
||||
|
||||
Vector2D Vector2D::rotated(const double& ang) const {
|
||||
const double COS = std::abs(std::cos(ang));
|
||||
const double SIN = std::abs(std::sin(ang));
|
||||
return Vector2D(x * COS + y * SIN, x * SIN + y * COS);
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
class Vector2D {
|
||||
public:
|
||||
Vector2D(double, double);
|
||||
Vector2D();
|
||||
Vector2D(const Hyprlang::VEC2& v) {
|
||||
x = v.x;
|
||||
y = v.y;
|
||||
}
|
||||
~Vector2D();
|
||||
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
|
||||
// returns the scale
|
||||
double normalize();
|
||||
|
||||
Vector2D operator+(const Vector2D& a) const {
|
||||
return Vector2D(this->x + a.x, this->y + a.y);
|
||||
}
|
||||
Vector2D operator-(const Vector2D& a) const {
|
||||
return Vector2D(this->x - a.x, this->y - a.y);
|
||||
}
|
||||
Vector2D operator-() const {
|
||||
return Vector2D(-this->x, -this->y);
|
||||
}
|
||||
Vector2D operator*(const double& a) const {
|
||||
return Vector2D(this->x * a, this->y * a);
|
||||
}
|
||||
Vector2D operator/(const double& a) const {
|
||||
return Vector2D(this->x / a, this->y / a);
|
||||
}
|
||||
|
||||
bool operator==(const Vector2D& a) const {
|
||||
return a.x == x && a.y == y;
|
||||
}
|
||||
|
||||
bool operator!=(const Vector2D& a) const {
|
||||
return a.x != x || a.y != y;
|
||||
}
|
||||
|
||||
Vector2D operator*(const Vector2D& a) const {
|
||||
return Vector2D(this->x * a.x, this->y * a.y);
|
||||
}
|
||||
|
||||
Vector2D operator/(const Vector2D& a) const {
|
||||
return Vector2D(this->x / a.x, this->y / a.y);
|
||||
}
|
||||
|
||||
bool operator>(const Vector2D& a) const {
|
||||
return this->x > a.x && this->y > a.y;
|
||||
}
|
||||
|
||||
bool operator<(const Vector2D& a) const {
|
||||
return this->x < a.x && this->y < a.y;
|
||||
}
|
||||
Vector2D& operator+=(const Vector2D& a) {
|
||||
this->x += a.x;
|
||||
this->y += a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator-=(const Vector2D& a) {
|
||||
this->x -= a.x;
|
||||
this->y -= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator*=(const Vector2D& a) {
|
||||
this->x *= a.x;
|
||||
this->y *= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator/=(const Vector2D& a) {
|
||||
this->x /= a.x;
|
||||
this->y /= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator*=(const double& a) {
|
||||
this->x *= a;
|
||||
this->y *= a;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator/=(const double& a) {
|
||||
this->x /= a;
|
||||
this->y /= a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
double distance(const Vector2D& other) const;
|
||||
double size() const;
|
||||
Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const;
|
||||
|
||||
Vector2D floor() const;
|
||||
Vector2D round() const;
|
||||
|
||||
Vector2D getComponentMax(const Vector2D& other) const;
|
||||
Vector2D rotated(const double& ang) const;
|
||||
};
|
||||
|
||||
/**
|
||||
format specification
|
||||
- 'j', as json array
|
||||
- 'X', same as std::format("{}x{}", vec.x, vec.y)
|
||||
- number, floating point precision, use `0` to format as integer
|
||||
*/
|
||||
// absolutely ridiculous formatter spec parsing
|
||||
#define FORMAT_PARSE(specs__, type__) \
|
||||
template <typename FormatContext> \
|
||||
constexpr auto parse(FormatContext& ctx) { \
|
||||
auto it = ctx.begin(); \
|
||||
for (; it != ctx.end() && *it != '}'; it++) { \
|
||||
switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \
|
||||
} \
|
||||
return it; \
|
||||
}
|
||||
|
||||
#define FORMAT_FLAG(spec__, flag__) \
|
||||
case spec__: (flag__) = true; break;
|
||||
|
||||
#define FORMAT_NUMBER(buf__) \
|
||||
case '0': \
|
||||
case '1': \
|
||||
case '2': \
|
||||
case '3': \
|
||||
case '4': \
|
||||
case '5': \
|
||||
case '6': \
|
||||
case '7': \
|
||||
case '8': \
|
||||
case '9': (buf__).push_back(*it); break;
|
||||
template <typename CharT>
|
||||
struct std::formatter<Vector2D, CharT> : std::formatter<CharT> {
|
||||
bool formatJson = false;
|
||||
bool formatX = false;
|
||||
std::string precision = "";
|
||||
FORMAT_PARSE(FORMAT_FLAG('j', formatJson) //
|
||||
FORMAT_FLAG('X', formatX) //
|
||||
FORMAT_NUMBER(precision),
|
||||
Vector2D)
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Vector2D& vec, FormatContext& ctx) const {
|
||||
std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision);
|
||||
|
||||
if (formatJson)
|
||||
formatString = std::format("[{0}, {0}]", formatString);
|
||||
else if (formatX)
|
||||
formatString = std::format("{0}x{0}", formatString);
|
||||
else
|
||||
formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString);
|
||||
try {
|
||||
string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y));
|
||||
return std::format_to(ctx.out(), "{}", buf);
|
||||
} catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); }
|
||||
}
|
||||
};
|
|
@ -1,84 +0,0 @@
|
|||
#include "Webp.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <webp/decode.h>
|
||||
|
||||
cairo_surface_t* WEBP::createSurfaceFromWEBP(const std::filesystem::path& path) {
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
Debug::log(ERR, "createSurfaceFromWEBP: file doesn't exist??");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* imageRawData;
|
||||
|
||||
struct stat fileInfo = {};
|
||||
|
||||
const auto FD = open(path.c_str(), O_RDONLY);
|
||||
|
||||
fstat(FD, &fileInfo);
|
||||
|
||||
imageRawData = malloc(fileInfo.st_size);
|
||||
|
||||
read(FD, imageRawData, fileInfo.st_size);
|
||||
close(FD);
|
||||
|
||||
// now the WebP is in the memory
|
||||
|
||||
WebPDecoderConfig config;
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
Debug::log(CRIT, "WebPInitDecoderConfig Failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (WebPGetFeatures((const unsigned char*)imageRawData, fileInfo.st_size, &config.input) != VP8_STATUS_OK) {
|
||||
Debug::log(ERR, "createSurfaceFromWEBP: file is not webp format");
|
||||
free(imageRawData);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto HEIGHT = config.input.height;
|
||||
const auto WIDTH = config.input.width;
|
||||
|
||||
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT);
|
||||
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(CRIT, "createSurfaceFromWEBP: Cairo Failed (?)");
|
||||
cairo_surface_destroy(cairoSurface);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
config.output.colorspace = MODE_bgrA;
|
||||
#else
|
||||
config.output.colorspace = MODE_Argb;
|
||||
#endif
|
||||
|
||||
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
|
||||
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
|
||||
|
||||
config.options.no_fancy_upsampling = 1;
|
||||
config.output.u.RGBA.rgba = CAIRODATA;
|
||||
config.output.u.RGBA.stride = CAIROSTRIDE;
|
||||
config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT;
|
||||
config.output.is_external_memory = 1;
|
||||
config.output.width = WIDTH;
|
||||
config.output.height = HEIGHT;
|
||||
|
||||
if (WebPDecode((const unsigned char*)imageRawData, fileInfo.st_size, &config) != VP8_STATUS_OK) {
|
||||
Debug::log(CRIT, "createSurfaceFromWEBP: WebP Decode Failed (?)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cairo_surface_flush(cairoSurface);
|
||||
cairo_surface_mark_dirty(cairoSurface);
|
||||
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_PNG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
|
||||
|
||||
WebPFreeDecBuffer(&config.output);
|
||||
|
||||
return cairoSurface;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
namespace WEBP {
|
||||
cairo_surface_t* createSurfaceFromWEBP(const std::filesystem::path&);
|
||||
};
|
60
src/main.cpp
60
src/main.cpp
|
@ -1,39 +1,64 @@
|
|||
|
||||
#include "config/ConfigManager.hpp"
|
||||
#include "core/hyprlock.hpp"
|
||||
#include "helpers/Log.hpp"
|
||||
#include "core/AnimationManager.hpp"
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
void help() {
|
||||
std::cout << "Usage: hyprlock [options]\n\n"
|
||||
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 (display) - Specify the Wayland display to connect to\n"
|
||||
" --display NAME - Specify the Wayland display to connect to\n"
|
||||
" --immediate - Lock immediately, ignoring any configured grace period\n"
|
||||
" -h, --help - Show this help message\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::cerr << "Error: Missing value for " << flag << " option.\n";
|
||||
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 immediate = false;
|
||||
bool showHelp = false;
|
||||
bool immediateRender = false;
|
||||
bool noFadeIn = false;
|
||||
|
||||
std::vector<std::string> args(argv, argv + argc);
|
||||
|
||||
for (std::size_t i = 1; i < args.size(); ++i) {
|
||||
const std::string arg = argv[i];
|
||||
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg == "--version" || arg == "-V") {
|
||||
printVersion();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg == "--verbose" || arg == "-v")
|
||||
Debug::verbose = true;
|
||||
|
||||
|
@ -55,24 +80,24 @@ int main(int argc, char** argv, char** envp) {
|
|||
} else if (arg == "--immediate")
|
||||
immediate = true;
|
||||
|
||||
else if (arg == "--help" || arg == "-h") {
|
||||
showHelp = true;
|
||||
break;
|
||||
else if (arg == "--immediate-render")
|
||||
immediateRender = true;
|
||||
|
||||
} else {
|
||||
std::cerr << "Unknown option: " << arg << "\n";
|
||||
else if (arg == "--no-fade-in")
|
||||
noFadeIn = true;
|
||||
|
||||
else {
|
||||
std::println(stderr, "Unknown option: {}", arg);
|
||||
help();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (showHelp) {
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
printVersion();
|
||||
g_pAnimationManager = makeUnique<CHyprlockAnimationManager>();
|
||||
|
||||
try {
|
||||
g_pConfigManager = std::make_unique<CConfigManager>(configPath);
|
||||
g_pConfigManager = makeUnique<CConfigManager>(configPath);
|
||||
g_pConfigManager->init();
|
||||
} catch (const std::exception& ex) {
|
||||
Debug::log(CRIT, "ConfigManager threw: {}", ex.what());
|
||||
|
@ -82,8 +107,11 @@ int main(int argc, char** argv, char** envp) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (noFadeIn || immediate)
|
||||
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
|
||||
|
||||
try {
|
||||
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate);
|
||||
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediate, immediateRender);
|
||||
g_pHyprlock->run();
|
||||
} catch (const std::exception& ex) {
|
||||
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());
|
||||
|
|
|
@ -2,16 +2,25 @@
|
|||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include <cairo/cairo.h>
|
||||
#include <magic.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../helpers/Jpeg.hpp"
|
||||
#include "../helpers/Webp.hpp"
|
||||
#include "src/helpers/Color.hpp"
|
||||
#include "src/helpers/Log.hpp"
|
||||
#include <hyprgraphics/image/Image.hpp>
|
||||
using namespace Hyprgraphics;
|
||||
|
||||
CAsyncResourceGatherer::CAsyncResourceGatherer() {
|
||||
if (g_pHyprlock->getScreencopy())
|
||||
enqueueScreencopyFrames();
|
||||
|
||||
initialGatherThread = std::thread([this]() { this->gather(); });
|
||||
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::enqueueScreencopyFrames() {
|
||||
// some things can't be done async :(
|
||||
// gather background textures when needed
|
||||
|
||||
|
@ -38,22 +47,13 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() {
|
|||
}
|
||||
|
||||
for (auto& mon : mons) {
|
||||
const auto MON = std::find_if(g_pHyprlock->m_vOutputs.begin(), g_pHyprlock->m_vOutputs.end(),
|
||||
[mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
|
||||
const auto MON = std::ranges::find_if(g_pHyprlock->m_vOutputs, [mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
|
||||
|
||||
if (MON == g_pHyprlock->m_vOutputs.end())
|
||||
continue;
|
||||
|
||||
const auto PMONITOR = MON->get();
|
||||
|
||||
dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR));
|
||||
scframes.emplace_back(makeUnique<CScreencopyFrame>(*MON));
|
||||
}
|
||||
|
||||
asyncLoopThread = std::thread([this]() {
|
||||
this->gather(); /* inital gather */
|
||||
this->asyncAssetSpinLock();
|
||||
});
|
||||
asyncLoopThread.detach();
|
||||
}
|
||||
|
||||
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
|
||||
|
@ -69,20 +69,24 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
|
|||
}
|
||||
};
|
||||
|
||||
for (auto& dma : dmas) {
|
||||
if (id == dma->resourceID)
|
||||
return dma->asset.ready ? &dma->asset : nullptr;
|
||||
for (auto& frame : scframes) {
|
||||
if (id == frame->m_resourceID)
|
||||
return frame->m_asset.ready ? &frame->m_asset : nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enum class FileType {
|
||||
PNG,
|
||||
JPEG,
|
||||
WEBP,
|
||||
UNKNOWN,
|
||||
};
|
||||
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();
|
||||
|
@ -111,71 +115,22 @@ void CAsyncResourceGatherer::gather() {
|
|||
continue;
|
||||
|
||||
std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path;
|
||||
std::filesystem::path ABSOLUTEPATH(absolutePath(path, ""));
|
||||
|
||||
// determine the file type
|
||||
std::string ext = ABSOLUTEPATH.extension().string();
|
||||
// convert the extension to lower case
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { return c <= 'Z' && c >= 'A' ? c - ('Z' - 'z') : c; });
|
||||
// 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;
|
||||
|
||||
FileType ft = FileType::UNKNOWN;
|
||||
Debug::log(WARN, "Extension: {}", ext);
|
||||
if (ext == ".png")
|
||||
ft = FileType::PNG;
|
||||
else if (ext == ".jpg" || ext == ".jpeg")
|
||||
ft = FileType::JPEG;
|
||||
else if (ext == ".webp")
|
||||
ft = FileType::WEBP;
|
||||
else {
|
||||
// magic is slow, so only use it when no recognized extension is found
|
||||
auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
|
||||
magic_load(handle, nullptr);
|
||||
|
||||
const auto type_str = std::string(magic_file(handle, ABSOLUTEPATH.c_str()));
|
||||
const auto first_word = type_str.substr(0, type_str.find(" "));
|
||||
magic_close(handle);
|
||||
|
||||
if (first_word == "PNG")
|
||||
ft = FileType::PNG;
|
||||
else if (first_word == "JPEG")
|
||||
ft = FileType::JPEG;
|
||||
else if (first_word == "RIFF" && type_str.find("Web/P image") != std::string::npos)
|
||||
ft = FileType::WEBP;
|
||||
}
|
||||
|
||||
// preload bg img
|
||||
cairo_surface_t* CAIROISURFACE = nullptr;
|
||||
switch (ft) {
|
||||
case FileType::PNG: CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str()); break;
|
||||
case FileType::JPEG: CAIROISURFACE = JPEG::createSurfaceFromJPEG(ABSOLUTEPATH); break;
|
||||
case FileType::WEBP: CAIROISURFACE = WEBP::createSurfaceFromWEBP(ABSOLUTEPATH); break;
|
||||
default: Debug::log(ERR, "unrecognized image format of {}", path.c_str()); continue;
|
||||
}
|
||||
|
||||
if (CAIROISURFACE == nullptr)
|
||||
continue;
|
||||
|
||||
const auto CAIRO = cairo_create(CAIROISURFACE);
|
||||
cairo_scale(CAIRO, 1, 1);
|
||||
|
||||
const auto TARGET = &preloadTargets.emplace_back(CAsyncResourceGatherer::SPreloadTarget{});
|
||||
|
||||
TARGET->size = {cairo_image_surface_get_width(CAIROISURFACE), cairo_image_surface_get_height(CAIROISURFACE)};
|
||||
TARGET->type = TARGET_IMAGE;
|
||||
TARGET->id = id;
|
||||
|
||||
const auto DATA = cairo_image_surface_get_data(CAIROISURFACE);
|
||||
TARGET->cairo = CAIRO;
|
||||
TARGET->cairosurface = CAIROISURFACE;
|
||||
TARGET->data = DATA;
|
||||
renderImage(rq);
|
||||
}
|
||||
}
|
||||
|
||||
while (std::any_of(dmas.begin(), dmas.end(), [](const auto& d) { return !d->asset.ready; })) {
|
||||
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));
|
||||
}
|
||||
|
||||
ready = true;
|
||||
gathered = true;
|
||||
}
|
||||
|
||||
bool CAsyncResourceGatherer::apply() {
|
||||
|
@ -194,8 +149,8 @@ bool CAsyncResourceGatherer::apply() {
|
|||
if (t.type == TARGET_IMAGE) {
|
||||
const auto ASSET = &assets[t.id];
|
||||
|
||||
const auto SURFACESTATUS = cairo_surface_status((cairo_surface_t*)t.cairosurface);
|
||||
const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface);
|
||||
const 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;
|
||||
|
@ -218,14 +173,11 @@ bool CAsyncResourceGatherer::apply() {
|
|||
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
|
||||
|
||||
cairo_destroy((cairo_t*)t.cairo);
|
||||
cairo_surface_destroy((cairo_surface_t*)t.cairosurface);
|
||||
|
||||
} else {
|
||||
Debug::log(ERR, "Unsupported type in ::apply() {}", (int)t.type);
|
||||
}
|
||||
t.cairosurface.reset();
|
||||
} else
|
||||
Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type);
|
||||
}
|
||||
|
||||
applied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -234,16 +186,21 @@ void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
|
|||
target.type = TARGET_IMAGE;
|
||||
target.id = rq.id;
|
||||
|
||||
const auto ABSOLUTEPATH = absolutePath(rq.asset, "");
|
||||
const auto CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str());
|
||||
std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, ""));
|
||||
const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH);
|
||||
|
||||
const auto CAIRO = cairo_create(CAIROISURFACE);
|
||||
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 = cairo_image_surface_get_data(CAIROISURFACE);
|
||||
target.size = {(double)cairo_image_surface_get_width(CAIROISURFACE), (double)cairo_image_surface_get_height(CAIROISURFACE)};
|
||||
target.data = CAIROISURFACE->data();
|
||||
target.size = CAIROISURFACE->size();
|
||||
|
||||
std::lock_guard lg{preloadTargetsMutex};
|
||||
preloadTargets.push_back(target);
|
||||
|
@ -255,20 +212,20 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
|||
target.id = rq.id;
|
||||
|
||||
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
|
||||
const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CColor>(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0);
|
||||
const 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 auto* const TRIM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:text_trim");
|
||||
std::string TEXT = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
|
||||
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);
|
||||
if (*TRIM) {
|
||||
text.erase(0, text.find_first_not_of(" \n\r\t"));
|
||||
text.erase(text.find_last_not_of(" \n\r\t") + 1);
|
||||
}
|
||||
|
||||
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */);
|
||||
auto CAIRO = cairo_create(CAIROSURFACE);
|
||||
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);
|
||||
|
@ -292,12 +249,12 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
|||
PangoAttrList* attrList = nullptr;
|
||||
GError* gError = nullptr;
|
||||
char* buf = nullptr;
|
||||
if (pango_parse_markup(TEXT.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
|
||||
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);
|
||||
Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message);
|
||||
g_error_free(gError);
|
||||
pango_layout_set_text(layout, TEXT.c_str(), -1);
|
||||
pango_layout_set_text(layout, text.c_str(), -1);
|
||||
}
|
||||
|
||||
if (!attrList)
|
||||
|
@ -315,9 +272,8 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
|||
|
||||
// TODO: avoid this?
|
||||
cairo_destroy(CAIRO);
|
||||
cairo_surface_destroy(CAIROSURFACE);
|
||||
CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE);
|
||||
CAIRO = cairo_create(CAIROSURFACE);
|
||||
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);
|
||||
|
@ -333,33 +289,22 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
|||
|
||||
g_object_unref(layout);
|
||||
|
||||
cairo_surface_flush(CAIROSURFACE);
|
||||
cairo_surface_flush(CAIROSURFACE->cairo());
|
||||
|
||||
target.cairo = CAIRO;
|
||||
target.cairosurface = CAIROSURFACE;
|
||||
target.data = cairo_image_surface_get_data(CAIROSURFACE);
|
||||
target.size = {layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE};
|
||||
target.data = CAIROSURFACE->data();
|
||||
target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE};
|
||||
|
||||
std::lock_guard lg{preloadTargetsMutex};
|
||||
preloadTargets.push_back(target);
|
||||
}
|
||||
|
||||
struct STimerCallbackData {
|
||||
void (*cb)(void*) = nullptr;
|
||||
void* data = nullptr;
|
||||
};
|
||||
|
||||
static void timerCallback(std::shared_ptr<CTimer> self, void* data_) {
|
||||
auto data = (STimerCallbackData*)data_;
|
||||
data->cb(data->data);
|
||||
delete data;
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::asyncAssetSpinLock() {
|
||||
while (!g_pHyprlock->m_bTerminate) {
|
||||
|
||||
std::unique_lock lk(asyncLoopState.requestsMutex);
|
||||
if (asyncLoopState.pending == false) // avoid a lock if a thread managed to request something already since we .unlock()ed
|
||||
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;
|
||||
|
@ -376,6 +321,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
|
|||
|
||||
// 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) {
|
||||
|
@ -387,14 +334,14 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
|
|||
|
||||
// plant timer for callback
|
||||
if (r.callback)
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), timerCallback, new STimerCallbackData{r.callback, r.callbackData});
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
dmas.clear();
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -407,11 +354,14 @@ void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* 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();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "Shader.hpp"
|
||||
#include "../helpers/Box.hpp"
|
||||
#include "../helpers/Color.hpp"
|
||||
#include "DMAFrame.hpp"
|
||||
#include "Texture.hpp"
|
||||
#include "Screencopy.hpp"
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
@ -12,12 +8,12 @@
|
|||
#include <condition_variable>
|
||||
#include <any>
|
||||
#include "Shared.hpp"
|
||||
#include <hyprgraphics/cairo/CairoSurface.hpp>
|
||||
|
||||
class CAsyncResourceGatherer {
|
||||
public:
|
||||
CAsyncResourceGatherer();
|
||||
std::atomic<bool> ready = false;
|
||||
std::atomic<bool> applied = false;
|
||||
std::atomic<bool> gathered = false;
|
||||
|
||||
std::atomic<float> progress = 0;
|
||||
|
||||
|
@ -41,8 +37,7 @@ class CAsyncResourceGatherer {
|
|||
// optional. Callbacks will be dispatched from the main thread,
|
||||
// so wayland/gl calls are OK.
|
||||
// will fire once the resource is fully loaded and ready.
|
||||
void (*callback)(void*) = nullptr;
|
||||
void* callbackData = nullptr;
|
||||
std::function<void()> callback = nullptr;
|
||||
};
|
||||
|
||||
void requestAsyncAssetPreload(const SPreloadRequest& request);
|
||||
|
@ -52,6 +47,7 @@ class CAsyncResourceGatherer {
|
|||
|
||||
private:
|
||||
std::thread asyncLoopThread;
|
||||
std::thread initialGatherThread;
|
||||
|
||||
void asyncAssetSpinLock();
|
||||
void renderText(const SPreloadRequest& rq);
|
||||
|
@ -71,14 +67,14 @@ class CAsyncResourceGatherer {
|
|||
eTargetType type = TARGET_IMAGE;
|
||||
std::string id = "";
|
||||
|
||||
void* data;
|
||||
void* cairo;
|
||||
void* cairosurface;
|
||||
void* data = nullptr;
|
||||
void* cairo = nullptr;
|
||||
SP<Hyprgraphics::CCairoSurface> cairosurface;
|
||||
|
||||
Vector2D size;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<CDMAFrame>> dmas;
|
||||
std::vector<UP<CScreencopyFrame>> scframes;
|
||||
|
||||
std::vector<SPreloadTarget> preloadTargets;
|
||||
std::mutex preloadTargetsMutex;
|
||||
|
@ -86,4 +82,5 @@ class CAsyncResourceGatherer {
|
|||
std::unordered_map<std::string, SPreloadedAsset> assets;
|
||||
|
||||
void gather();
|
||||
void enqueueScreencopyFrames();
|
||||
};
|
||||
|
|
|
@ -1,253 +0,0 @@
|
|||
#include "DMAFrame.hpp"
|
||||
#include "wlr-screencopy-unstable-v1-protocol.h"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
|
||||
#include <EGL/eglext.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <GLES3/gl32.h>
|
||||
#include <GLES3/gl3ext.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
|
||||
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
|
||||
|
||||
//
|
||||
static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||
const auto PDATA = (SScreencopyData*)data;
|
||||
|
||||
Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PDATA);
|
||||
|
||||
PDATA->size = stride * height;
|
||||
PDATA->stride = stride;
|
||||
}
|
||||
|
||||
static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) {
|
||||
;
|
||||
}
|
||||
|
||||
static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||
const auto PDATA = (SScreencopyData*)data;
|
||||
|
||||
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PDATA);
|
||||
|
||||
if (!PDATA->frame->onBufferReady()) {
|
||||
Debug::log(ERR, "onBufferReady failed");
|
||||
return;
|
||||
}
|
||||
|
||||
zwlr_screencopy_frame_v1_destroy(frame);
|
||||
}
|
||||
|
||||
static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) {
|
||||
;
|
||||
}
|
||||
|
||||
static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
|
||||
;
|
||||
}
|
||||
|
||||
static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
|
||||
const auto PDATA = (SScreencopyData*)data;
|
||||
|
||||
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PDATA);
|
||||
|
||||
PDATA->w = width;
|
||||
PDATA->h = height;
|
||||
PDATA->fmt = format;
|
||||
|
||||
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
|
||||
}
|
||||
|
||||
static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) {
|
||||
const auto PDATA = (SScreencopyData*)data;
|
||||
|
||||
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PDATA);
|
||||
|
||||
if (!PDATA->frame->onBufferDone()) {
|
||||
Debug::log(ERR, "onBufferDone failed");
|
||||
return;
|
||||
}
|
||||
|
||||
zwlr_screencopy_frame_v1_copy(frame, PDATA->frame->wlBuffer);
|
||||
|
||||
Debug::log(TRACE, "[sc] wlr frame copied");
|
||||
}
|
||||
|
||||
static const zwlr_screencopy_frame_v1_listener wlrFrameListener = {
|
||||
.buffer = wlrOnBuffer,
|
||||
.flags = wlrOnFlags,
|
||||
.ready = wlrOnReady,
|
||||
.failed = wlrOnFailed,
|
||||
.damage = wlrOnDamage,
|
||||
.linux_dmabuf = wlrOnDmabuf,
|
||||
.buffer_done = wlrOnBufferDone,
|
||||
};
|
||||
|
||||
std::string CDMAFrame::getResourceId(COutput* output) {
|
||||
return std::format("dma:{}-{}x{}", output->stringPort, output->size.x, output->size.y);
|
||||
}
|
||||
|
||||
CDMAFrame::CDMAFrame(COutput* output_) {
|
||||
resourceID = getResourceId(output_);
|
||||
|
||||
if (!glEGLImageTargetTexture2DOES) {
|
||||
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
if (!glEGLImageTargetTexture2DOES) {
|
||||
Debug::log(ERR, "No glEGLImageTargetTexture2DOES??");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!eglQueryDmaBufModifiersEXT)
|
||||
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
|
||||
|
||||
// firstly, plant a listener for the frame
|
||||
frameCb = zwlr_screencopy_manager_v1_capture_output(g_pHyprlock->getScreencopy(), false, output_->output);
|
||||
|
||||
scdata.frame = this;
|
||||
|
||||
zwlr_screencopy_frame_v1_add_listener(frameCb, &wlrFrameListener, &scdata);
|
||||
}
|
||||
|
||||
CDMAFrame::~CDMAFrame() {
|
||||
if (g_pEGL)
|
||||
eglDestroyImage(g_pEGL->eglDisplay, image);
|
||||
|
||||
// leaks bo and stuff but lives throughout so for now who cares
|
||||
}
|
||||
|
||||
bool CDMAFrame::onBufferDone() {
|
||||
uint32_t flags = GBM_BO_USE_RENDERING;
|
||||
|
||||
if (!eglQueryDmaBufModifiersEXT) {
|
||||
Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support");
|
||||
bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags);
|
||||
} else {
|
||||
std::vector<uint64_t> mods;
|
||||
mods.resize(64);
|
||||
std::vector<EGLBoolean> externalOnly;
|
||||
externalOnly.resize(64);
|
||||
int num = 0;
|
||||
if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, scdata.fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) {
|
||||
Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo");
|
||||
bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags);
|
||||
} else {
|
||||
Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num);
|
||||
std::vector<uint64_t> goodMods;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
if (externalOnly[i]) {
|
||||
Debug::log(TRACE, "Modifier {:x} failed test", mods[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug::log(TRACE, "Modifier {:x} passed test", mods[i]);
|
||||
goodMods.push_back(mods[i]);
|
||||
}
|
||||
|
||||
uint64_t zero = 0;
|
||||
bool hasLinear = std::find(goodMods.begin(), goodMods.end(), 0) != goodMods.end();
|
||||
|
||||
bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, hasLinear ? &zero : goodMods.data(), hasLinear ? 1 : goodMods.size(),
|
||||
flags);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bo) {
|
||||
Debug::log(ERR, "Couldn't create a drm buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
planes = gbm_bo_get_plane_count(bo);
|
||||
|
||||
uint64_t mod = gbm_bo_get_modifier(bo);
|
||||
Debug::log(LOG, "bo chose modifier {:x}", mod);
|
||||
|
||||
zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pHyprlock->dma.linuxDmabuf);
|
||||
if (!params) {
|
||||
Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed");
|
||||
gbm_bo_destroy(bo);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t plane = 0; plane < (size_t)planes; plane++) {
|
||||
size[plane] = 0;
|
||||
stride[plane] = gbm_bo_get_stride_for_plane(bo, plane);
|
||||
offset[plane] = gbm_bo_get_offset(bo, plane);
|
||||
fd[plane] = gbm_bo_get_fd_for_plane(bo, plane);
|
||||
|
||||
if (fd[plane] < 0) {
|
||||
Debug::log(ERR, "gbm_bo_get_fd_for_plane failed");
|
||||
zwp_linux_buffer_params_v1_destroy(params);
|
||||
gbm_bo_destroy(bo);
|
||||
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
|
||||
close(fd[plane_tmp]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
zwp_linux_buffer_params_v1_add(params, fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff);
|
||||
}
|
||||
|
||||
wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, scdata.w, scdata.h, scdata.fmt, 0);
|
||||
zwp_linux_buffer_params_v1_destroy(params);
|
||||
|
||||
if (!wlBuffer) {
|
||||
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
|
||||
gbm_bo_destroy(bo);
|
||||
for (size_t plane = 0; plane < (size_t)planes; plane++)
|
||||
close(fd[plane]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDMAFrame::onBufferReady() {
|
||||
static const int general_attribs = 3;
|
||||
static const int plane_attribs = 5;
|
||||
static const int entries_per_attrib = 2;
|
||||
EGLAttrib attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1];
|
||||
int attr = 0;
|
||||
Vector2D size{scdata.w, scdata.h};
|
||||
|
||||
attribs[attr++] = EGL_WIDTH;
|
||||
attribs[attr++] = size.x;
|
||||
attribs[attr++] = EGL_HEIGHT;
|
||||
attribs[attr++] = size.y;
|
||||
attribs[attr++] = EGL_LINUX_DRM_FOURCC_EXT;
|
||||
attribs[attr++] = scdata.fmt;
|
||||
attribs[attr++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
||||
attribs[attr++] = fd[0];
|
||||
attribs[attr++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
||||
attribs[attr++] = offset[0];
|
||||
attribs[attr++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
||||
attribs[attr++] = stride[0];
|
||||
attribs[attr] = EGL_NONE;
|
||||
|
||||
image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
|
||||
|
||||
if (image == EGL_NO_IMAGE) {
|
||||
Debug::log(ERR, "failed creating an egl image");
|
||||
return false;
|
||||
}
|
||||
|
||||
asset.texture.allocate();
|
||||
asset.texture.m_vSize = size;
|
||||
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
Debug::log(LOG, "Got dma frame with size {}", size);
|
||||
|
||||
asset.ready = true;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../core/Output.hpp"
|
||||
#include <gbm.h>
|
||||
#include "Texture.hpp"
|
||||
#include "Shared.hpp"
|
||||
|
||||
struct zwlr_screencopy_frame_v1;
|
||||
|
||||
class CDMAFrame;
|
||||
|
||||
struct SScreencopyData {
|
||||
int w = 0, h = 0;
|
||||
uint32_t fmt;
|
||||
size_t size;
|
||||
size_t stride;
|
||||
CDMAFrame* frame = nullptr;
|
||||
};
|
||||
|
||||
class CDMAFrame {
|
||||
public:
|
||||
static std::string getResourceId(COutput* output);
|
||||
|
||||
CDMAFrame(COutput* mon);
|
||||
~CDMAFrame();
|
||||
|
||||
bool onBufferDone();
|
||||
bool onBufferReady();
|
||||
|
||||
wl_buffer* wlBuffer = nullptr;
|
||||
|
||||
std::string resourceID;
|
||||
|
||||
SPreloadedAsset asset;
|
||||
|
||||
private:
|
||||
gbm_bo* bo = nullptr;
|
||||
|
||||
int planes = 0;
|
||||
|
||||
int fd[4];
|
||||
uint32_t size[4], stride[4], offset[4];
|
||||
|
||||
zwlr_screencopy_frame_v1* frameCb = nullptr;
|
||||
SScreencopyData scdata;
|
||||
|
||||
EGLImage image = nullptr;
|
||||
};
|
|
@ -41,14 +41,14 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
|
|||
|
||||
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, 0);
|
||||
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, 0);
|
||||
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);
|
||||
|
||||
|
@ -61,7 +61,7 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
|
|||
abort();
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Framebuffer created, status {}", status);
|
||||
Debug::log(TRACE, "Framebuffer created, status {}", status);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
@ -73,15 +73,20 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
|
|||
}
|
||||
|
||||
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, 0);
|
||||
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!", status);
|
||||
RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo! (FB status: {})", status);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
@ -99,15 +104,19 @@ void CFramebuffer::release() {
|
|||
if (m_cTex.m_iTexID)
|
||||
glDeleteTextures(1, &m_cTex.m_iTexID);
|
||||
|
||||
if (m_pStencilTex && m_pStencilTex->m_iTexID)
|
||||
glDeleteTextures(1, &m_pStencilTex->m_iTexID);
|
||||
|
||||
m_cTex.m_iTexID = 0;
|
||||
m_iFb = -1;
|
||||
m_vSize = Vector2D();
|
||||
m_pStencilTex = nullptr;
|
||||
}
|
||||
|
||||
CFramebuffer::~CFramebuffer() {
|
||||
release();
|
||||
}
|
||||
|
||||
bool CFramebuffer::isAllocated() {
|
||||
bool CFramebuffer::isAllocated() const {
|
||||
return m_iFb != (GLuint)-1;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "../helpers/Vector2D.hpp"
|
||||
#include "../helpers/Math.hpp"
|
||||
#include <GLES3/gl32.h>
|
||||
#include "Texture.hpp"
|
||||
|
||||
|
@ -13,7 +13,7 @@ class CFramebuffer {
|
|||
void bind() const;
|
||||
void release();
|
||||
void reset();
|
||||
bool isAllocated();
|
||||
bool isAllocated() const;
|
||||
|
||||
Vector2D m_vSize;
|
||||
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
#include "Renderer.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include "Shaders.hpp"
|
||||
#include "Screencopy.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../helpers/Color.hpp"
|
||||
#include "../core/AnimationManager.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include "../core/Output.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../renderer/DMAFrame.hpp"
|
||||
#include "mtx.hpp"
|
||||
|
||||
#include "../helpers/Color.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include <GLES3/gl32.h>
|
||||
#include <GLES3/gl3ext.h>
|
||||
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Shaders.hpp"
|
||||
|
||||
#include "widgets/PasswordInputField.hpp"
|
||||
#include "widgets/Background.hpp"
|
||||
#include "widgets/Label.hpp"
|
||||
|
@ -32,7 +30,7 @@ GLuint compileShader(const GLuint& type, std::string src) {
|
|||
|
||||
auto shaderSource = src.c_str();
|
||||
|
||||
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
|
||||
glShaderSource(shader, 1, &shaderSource, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint ok;
|
||||
|
@ -46,11 +44,11 @@ GLuint compileShader(const GLuint& type, std::string src) {
|
|||
GLuint createProgram(const std::string& vert, const std::string& frag) {
|
||||
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
|
||||
|
||||
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert.c_str());
|
||||
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.c_str());
|
||||
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag);
|
||||
|
||||
auto prog = glCreateProgram();
|
||||
glAttachShader(prog, vertCompiled);
|
||||
|
@ -80,7 +78,7 @@ CRenderer::CRenderer() {
|
|||
g_pEGL->makeCurrent(nullptr);
|
||||
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageCallback(glMessageCallbackA, 0);
|
||||
glDebugMessageCallback(glMessageCallbackA, nullptr);
|
||||
|
||||
GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC);
|
||||
rectShader.program = prog;
|
||||
|
@ -110,6 +108,27 @@ CRenderer::CRenderer() {
|
|||
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");
|
||||
|
@ -154,20 +173,35 @@ CRenderer::CRenderer() {
|
|||
blurFinishShader.colorizeTint = glGetUniformLocation(prog, "colorizeTint");
|
||||
blurFinishShader.boostA = glGetUniformLocation(prog, "boostA");
|
||||
|
||||
wlr_matrix_identity(projMatrix.data());
|
||||
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 = std::make_unique<CAsyncResourceGatherer>();
|
||||
asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>();
|
||||
|
||||
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
|
||||
}
|
||||
|
||||
static int frames = 0;
|
||||
|
||||
//
|
||||
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
|
||||
static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar");
|
||||
static auto* const PNOFADEIN = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_in");
|
||||
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
|
||||
|
||||
matrixProjection(projection.data(), surf.size.x, surf.size.y, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
|
||||
g_pEGL->makeCurrent(surf.eglSurface);
|
||||
glViewport(0, 0, surf.size.x, surf.size.y);
|
||||
|
@ -183,67 +217,37 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
|
|||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
SRenderFeedback feedback;
|
||||
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
|
||||
|
||||
float bga = asyncResourceGatherer->applied ?
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - gatheredAt).count() / 500000.0, 0.0, 1.0) :
|
||||
0.0;
|
||||
|
||||
if (!asyncResourceGatherer->ready) {
|
||||
// render status
|
||||
if (!**PDISABLEBAR) {
|
||||
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
|
||||
renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!asyncResourceGatherer->applied) {
|
||||
asyncResourceGatherer->apply();
|
||||
gatheredAt = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
if (**PNOFADEIN)
|
||||
bga = 1.0;
|
||||
|
||||
if (g_pHyprlock->m_bFadeStarted && !**PNOFADEOUT) {
|
||||
bga =
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(g_pHyprlock->m_tFadeEnds - std::chrono::system_clock::now()).count() / 500000.0 - 0.02, 0.0, 1.0);
|
||||
// - 0.02 so that the fade ends a little earlier than the final second
|
||||
}
|
||||
if (!WAITFORASSETS) {
|
||||
// render widgets
|
||||
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
|
||||
for (auto& w : *WIDGETS) {
|
||||
feedback.needsFrame = w->draw({bga}) || feedback.needsFrame;
|
||||
const auto WIDGETS = getOrCreateWidgetsFor(surf);
|
||||
for (auto& w : WIDGETS) {
|
||||
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
|
||||
}
|
||||
}
|
||||
|
||||
frames++;
|
||||
|
||||
Debug::log(TRACE, "frame {}", frames);
|
||||
|
||||
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->ready || bga < 1.0;
|
||||
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered;
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
return feedback;
|
||||
}
|
||||
|
||||
void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
|
||||
float matrix[9];
|
||||
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, box.rot,
|
||||
projMatrix.data()); // TODO: write own, don't use WLR here
|
||||
|
||||
float glMatrix[9];
|
||||
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
|
||||
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);
|
||||
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(box.x, box.y);
|
||||
const auto FULLSIZE = Vector2D(box.width, box.height);
|
||||
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);
|
||||
|
@ -259,13 +263,47 @@ void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
|
|||
glDisableVertexAttribArray(rectShader.posAttrib);
|
||||
}
|
||||
|
||||
void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional<wl_output_transform> tr) {
|
||||
float matrix[9];
|
||||
wlr_matrix_project_box(matrix, &box, tr.value_or(WL_OUTPUT_TRANSFORM_FLIPPED_180) /* ugh coordinate spaces */, box.rot,
|
||||
projMatrix.data()); // TODO: write own, don't use WLR here
|
||||
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);
|
||||
|
||||
float glMatrix[9];
|
||||
wlr_matrix_multiply(glMatrix, projection.data(), 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;
|
||||
|
||||
|
@ -274,11 +312,11 @@ void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int
|
|||
|
||||
glUseProgram(shader->program);
|
||||
|
||||
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix);
|
||||
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
|
||||
glUniform1i(shader->tex, 0);
|
||||
glUniform1f(shader->alpha, a);
|
||||
const auto TOPLEFT = Vector2D(box.x, box.y);
|
||||
const auto FULLSIZE = Vector2D(box.width, box.height);
|
||||
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);
|
||||
|
@ -303,68 +341,105 @@ void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int
|
|||
glBindTexture(tex.m_iTarget, 0);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) {
|
||||
if (!widgets.contains(surf)) {
|
||||
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<SP<IWidget>>& widgets) {
|
||||
const auto W = makeShared<Widget>();
|
||||
W->registerSelf(W);
|
||||
widgets.emplace_back(W);
|
||||
}
|
||||
|
||||
std::vector<SP<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::sort(CWIDGETS.begin(), CWIDGETS.end(), [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) {
|
||||
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 != surf->output->stringPort && !surf->output->stringDesc.starts_with(c.monitor))
|
||||
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") {
|
||||
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
|
||||
|
||||
std::string resourceID = "";
|
||||
if (PATH == "screenshot") {
|
||||
resourceID = CDMAFrame::getResourceId(surf->output);
|
||||
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
|
||||
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
|
||||
if (asyncResourceGatherer->ready) {
|
||||
if (!asyncResourceGatherer->getAssetByID(resourceID))
|
||||
resourceID = ""; // Fallback to solid color (background:color)
|
||||
}
|
||||
} else if (!PATH.empty())
|
||||
resourceID = "background:" + PATH;
|
||||
|
||||
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, surf->output, resourceID, c.values, PATH == "screenshot"));
|
||||
createWidget<CBackground>(widgets[surf.m_outputID]);
|
||||
} else if (c.type == "input-field") {
|
||||
widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(surf->size, c.values, surf->output->stringPort));
|
||||
createWidget<CPasswordInputField>(widgets[surf.m_outputID]);
|
||||
} else if (c.type == "label") {
|
||||
widgets[surf].emplace_back(std::make_unique<CLabel>(surf->size, c.values, surf->output->stringPort));
|
||||
createWidget<CLabel>(widgets[surf.m_outputID]);
|
||||
} else if (c.type == "shape") {
|
||||
widgets[surf].emplace_back(std::make_unique<CShape>(surf->size, c.values));
|
||||
createWidget<CShape>(widgets[surf.m_outputID]);
|
||||
} else if (c.type == "image") {
|
||||
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
|
||||
|
||||
std::string resourceID = "";
|
||||
if (!PATH.empty())
|
||||
resourceID = "image:" + PATH;
|
||||
|
||||
widgets[surf].emplace_back(std::make_unique<CImage>(surf->size, surf->output, resourceID, c.values));
|
||||
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];
|
||||
return widgets[surf.m_outputID];
|
||||
}
|
||||
|
||||
void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
float matrix[9];
|
||||
CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y};
|
||||
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
|
||||
projMatrix.data()); // TODO: write own, don't use WLR here
|
||||
|
||||
float glMatrix[9];
|
||||
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
|
||||
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);
|
||||
|
@ -385,7 +460,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
|
|||
|
||||
glUseProgram(blurPrepareShader.program);
|
||||
|
||||
glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix);
|
||||
glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
|
||||
glUniform1f(blurPrepareShader.contrast, params.contrast);
|
||||
glUniform1f(blurPrepareShader.brightness, params.brightness);
|
||||
glUniform1i(blurPrepareShader.tex, 0);
|
||||
|
@ -420,7 +495,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
|
|||
glUseProgram(pShader->program);
|
||||
|
||||
// prep two shaders
|
||||
glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix);
|
||||
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));
|
||||
|
@ -476,7 +551,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
|
|||
|
||||
glUseProgram(blurFinishShader.program);
|
||||
|
||||
glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix);
|
||||
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());
|
||||
|
@ -505,7 +580,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
|
|||
|
||||
// finish
|
||||
outfb.bind();
|
||||
renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
|
@ -520,6 +595,30 @@ void CRenderer::popFb() {
|
|||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundFBs.empty() ? 0 : boundFBs.back());
|
||||
}
|
||||
|
||||
void CRenderer::removeWidgetsFor(const CSessionLockSurface* surf) {
|
||||
widgets.erase(surf);
|
||||
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, bool immediate) {
|
||||
if (immediate)
|
||||
opacity->setValueAndWarp(0.f);
|
||||
else
|
||||
*opacity = 0.f;
|
||||
|
||||
if (unlock)
|
||||
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "../core/LockSurface.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include "../helpers/Box.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<const CSessionLockSurface*, std::vector<std::unique_ptr<IWidget>>> widgetMap_t;
|
||||
typedef std::unordered_map<OUTPUTID, std::vector<SP<IWidget>>> widgetMap_t;
|
||||
|
||||
class CRenderer {
|
||||
public:
|
||||
|
@ -25,40 +25,49 @@ class CRenderer {
|
|||
struct SBlurParams {
|
||||
int size = 0, passes = 0;
|
||||
float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0;
|
||||
std::optional<CColor> colorize;
|
||||
std::optional<CHyprColor> colorize;
|
||||
float boostA = 1.0;
|
||||
};
|
||||
|
||||
SRenderFeedback renderLock(const CSessionLockSurface& surface);
|
||||
SRenderFeedback renderLock(const CSessionLockSurface& surf);
|
||||
|
||||
void renderRect(const CBox& box, const CColor& col, int rounding = 0);
|
||||
void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional<wl_output_transform> tr = {});
|
||||
void 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);
|
||||
|
||||
std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer;
|
||||
std::chrono::system_clock::time_point gatheredAt;
|
||||
UP<CAsyncResourceGatherer> asyncResourceGatherer;
|
||||
std::chrono::system_clock::time_point firstFullFrameTime;
|
||||
|
||||
void pushFb(GLint fb);
|
||||
void popFb();
|
||||
|
||||
void removeWidgetsFor(const CSessionLockSurface* surf);
|
||||
void removeWidgetsFor(OUTPUTID id);
|
||||
void reconfigureWidgetsFor(OUTPUTID id);
|
||||
|
||||
void startFadeIn();
|
||||
void startFadeOut(bool unlock = false, bool immediate = true);
|
||||
std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
|
||||
|
||||
private:
|
||||
widgetMap_t widgets;
|
||||
|
||||
std::vector<std::unique_ptr<IWidget>>* getOrCreateWidgetsFor(const CSessionLockSurface* surf);
|
||||
|
||||
CShader rectShader;
|
||||
CShader texShader;
|
||||
CShader texMixShader;
|
||||
CShader blurShader1;
|
||||
CShader blurShader2;
|
||||
CShader blurPrepareShader;
|
||||
CShader blurFinishShader;
|
||||
CShader borderShader;
|
||||
|
||||
std::array<float, 9> projMatrix;
|
||||
std::array<float, 9> projection;
|
||||
Mat3x3 projMatrix = Mat3x3::identity();
|
||||
Mat3x3 projection;
|
||||
|
||||
PHLANIMVAR<float> opacity;
|
||||
|
||||
std::vector<GLint> boundFBs;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CRenderer> g_pRenderer;
|
||||
inline UP<CRenderer> g_pRenderer;
|
||||
|
|
475
src/renderer/Screencopy.cpp
Normal file
475
src/renderer/Screencopy.cpp
Normal file
|
@ -0,0 +1,475 @@
|
|||
#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 std::format("screencopy:{}-{}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;
|
||||
}
|
94
src/renderer/Screencopy.hpp
Normal file
94
src/renderer/Screencopy.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#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);
|
||||
|
||||
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;
|
||||
};
|
|
@ -13,7 +13,9 @@ class CShader {
|
|||
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;
|
||||
|
@ -41,7 +43,11 @@ class CShader {
|
|||
|
||||
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;
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
#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"#(
|
||||
|
@ -12,17 +16,21 @@ inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVar
|
|||
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 + 1.0)
|
||||
if (dist > radius + SMOOTHING_CONSTANT * 2.0)
|
||||
discard;
|
||||
|
||||
if (dist > radius - 1.0) {
|
||||
if (dist > radius - SMOOTHING_CONSTANT * 2.0) {
|
||||
float dist = length(pixCoord);
|
||||
|
||||
float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5);
|
||||
float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
|
||||
|
||||
)#" +
|
||||
colorVarName + R"#( = )#" + colorVarName + R"#( * normalized;
|
||||
|
@ -121,6 +129,49 @@ void main() {
|
|||
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;
|
||||
|
@ -362,3 +413,184 @@ void main() {
|
|||
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;
|
||||
}
|
||||
)#";
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "Texture.hpp"
|
||||
#include "../defines.hpp"
|
||||
|
||||
struct SPreloadedAsset {
|
||||
CTexture texture;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "Texture.hpp"
|
||||
|
||||
CTexture::CTexture() {
|
||||
// naffin'
|
||||
; // naffin'
|
||||
}
|
||||
|
||||
CTexture::~CTexture() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <GLES3/gl32.h>
|
||||
#include "../helpers/Vector2D.hpp"
|
||||
#include "../helpers/Math.hpp"
|
||||
|
||||
enum TEXTURETYPE {
|
||||
TEXTURE_INVALID, // Invalid
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
#include "Background.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../mtx.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
#include <chrono>
|
||||
#include <hyprlang.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <GLES3/gl32.h>
|
||||
|
||||
CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props, bool ss) :
|
||||
viewport(viewport_), resourceID(resourceID_), output(output_), isScreenshot(ss) {
|
||||
CBackground::~CBackground() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void CBackground::registerSelf(const SP<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"));
|
||||
|
@ -13,23 +29,104 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
|
|||
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"));
|
||||
crossFadeTime = std::any_cast<Hyprlang::FLOAT>(props.at("crossfade_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 = isScreenshot ? wlTransformToHyprutils(invertTransform(pOutput->transform)) : HYPRUTILS_TRANSFORM_NORMAL;
|
||||
|
||||
if (isScreenshot) {
|
||||
resourceID = CScreencopyFrame::getResourceId(pOutput);
|
||||
// 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) {
|
||||
if (!g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID))
|
||||
resourceID = ""; // Fallback to solid color (background:color)
|
||||
}
|
||||
|
||||
if (!g_pHyprlock->getScreencopy()) {
|
||||
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
|
||||
resourceID = "";
|
||||
}
|
||||
|
||||
} else if (!path.empty())
|
||||
resourceID = "background:" + path;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (fade) {
|
||||
if (fade->crossFadeTimer) {
|
||||
fade->crossFadeTimer->cancel();
|
||||
fade->crossFadeTimer.reset();
|
||||
}
|
||||
fade.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CBackground::renderRect(CHyprColor color) {
|
||||
CBox monbox = {0, 0, viewport.x, viewport.y};
|
||||
g_pRenderer->renderRect(monbox, color, 0);
|
||||
}
|
||||
|
||||
static void onReloadTimer(WP<CBackground> ref) {
|
||||
if (auto PBG = ref.lock(); PBG) {
|
||||
PBG->onReloadTimerUpdate();
|
||||
PBG->plantReloadTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onCrossFadeTimer(WP<CBackground> ref) {
|
||||
if (auto PBG = ref.lock(); PBG)
|
||||
PBG->onCrossFadeTimerUpdate();
|
||||
}
|
||||
|
||||
static void onAssetCallback(WP<CBackground> ref) {
|
||||
if (auto PBG = ref.lock(); PBG)
|
||||
PBG->startCrossFadeOrUpdateRender();
|
||||
}
|
||||
|
||||
bool CBackground::draw(const SRenderData& data) {
|
||||
|
||||
if (resourceID.empty()) {
|
||||
CBox monbox = {0, 0, viewport.x, viewport.y};
|
||||
CColor col = color;
|
||||
CHyprColor col = color;
|
||||
col.a *= data.opacity;
|
||||
g_pRenderer->renderRect(monbox, col, 0);
|
||||
renderRect(col);
|
||||
return data.opacity < 1.0;
|
||||
}
|
||||
|
||||
if (!asset)
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
|
||||
if (!asset)
|
||||
if (!asset) {
|
||||
CHyprColor col = color;
|
||||
col.a *= data.opacity;
|
||||
renderRect(col);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (asset->texture.m_iType == TEXTURE_INVALID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
|
@ -37,11 +134,14 @@ bool CBackground::draw(const SRenderData& data) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if ((blurPasses > 0 || isScreenshot) && !blurredFB.isAllocated()) {
|
||||
if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) {
|
||||
|
||||
if (firstRender)
|
||||
firstRender = false;
|
||||
|
||||
// make it brah
|
||||
Vector2D size = asset->texture.m_vSize;
|
||||
|
||||
if (output->transform % 2 == 1 && isScreenshot) {
|
||||
if (transform % 2 == 1 && isScreenshot) {
|
||||
size.x = asset->texture.m_vSize.y;
|
||||
size.y = asset->texture.m_vSize.x;
|
||||
}
|
||||
|
@ -59,15 +159,28 @@ bool CBackground::draw(const SRenderData& data) {
|
|||
else
|
||||
texbox.x = -(texbox.w - viewport.x) / 2.f;
|
||||
texbox.round();
|
||||
|
||||
if (!blurredFB.isAllocated())
|
||||
blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit
|
||||
|
||||
blurredFB.bind();
|
||||
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0,
|
||||
isScreenshot ?
|
||||
wlr_output_transform_invert(output->transform) :
|
||||
WL_OUTPUT_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs
|
||||
if (fade)
|
||||
g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), 0,
|
||||
transform);
|
||||
else
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, transform);
|
||||
|
||||
if (blurPasses > 0)
|
||||
g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness});
|
||||
g_pRenderer->blurFB(blurredFB,
|
||||
CRenderer::SBlurParams{.size = blurSize,
|
||||
.passes = blurPasses,
|
||||
.noise = noise,
|
||||
.contrast = contrast,
|
||||
.brightness = brightness,
|
||||
.vibrancy = vibrancy,
|
||||
.vibrancy_darkness = vibrancy_darkness});
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
|
@ -87,7 +200,116 @@ bool CBackground::draw(const SRenderData& data) {
|
|||
else
|
||||
texbox.x = -(texbox.w - viewport.x) / 2.f;
|
||||
texbox.round();
|
||||
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
|
||||
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
||||
|
||||
return data.opacity < 1.0;
|
||||
return fade || data.opacity < 1.0; // actively render during fading
|
||||
}
|
||||
|
||||
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::onCrossFadeTimerUpdate() {
|
||||
|
||||
// Animation done: Unload previous asset, deinitialize the fade and pass the asset
|
||||
|
||||
if (fade) {
|
||||
fade->crossFadeTimer.reset();
|
||||
fade.reset();
|
||||
}
|
||||
|
||||
if (blurPasses <= 0 && !isScreenshot)
|
||||
blurredFB.release();
|
||||
|
||||
asset = pendingAsset;
|
||||
resourceID = pendingResourceID;
|
||||
pendingResourceID = "";
|
||||
pendingAsset = nullptr;
|
||||
firstRender = true;
|
||||
|
||||
g_pHyprlock->renderOutput(outputPort);
|
||||
}
|
||||
|
||||
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::startCrossFadeOrUpdateRender() {
|
||||
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!");
|
||||
} else if (resourceID != pendingResourceID) {
|
||||
pendingAsset = newAsset;
|
||||
if (crossFadeTime > 0) {
|
||||
// Start a fade
|
||||
if (!fade)
|
||||
fade = makeUnique<SFade>(std::chrono::system_clock::now(), 0, nullptr);
|
||||
else {
|
||||
// Maybe we where already fading so reset it just in case, but should'nt be happening.
|
||||
if (fade->crossFadeTimer) {
|
||||
fade->crossFadeTimer->cancel();
|
||||
fade->crossFadeTimer.reset();
|
||||
}
|
||||
}
|
||||
fade->start = std::chrono::system_clock::now();
|
||||
fade->a = 0;
|
||||
fade->crossFadeTimer =
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), [REF = m_self](auto, auto) { onCrossFadeTimer(REF); }, nullptr);
|
||||
} else {
|
||||
onCrossFadeTimerUpdate();
|
||||
}
|
||||
}
|
||||
} 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(outputPort);
|
||||
}
|
|
@ -1,23 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "../Framebuffer.hpp"
|
||||
#include "../AsyncResourceGatherer.hpp"
|
||||
#include <hyprutils/math/Misc.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <any>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
struct SPreloadedAsset;
|
||||
class COutput;
|
||||
|
||||
struct SFade {
|
||||
std::chrono::system_clock::time_point start;
|
||||
float a = 0;
|
||||
std::shared_ptr<CTimer> crossFadeTimer = nullptr;
|
||||
};
|
||||
|
||||
class CBackground : public IWidget {
|
||||
public:
|
||||
CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_);
|
||||
CBackground() = default;
|
||||
~CBackground();
|
||||
|
||||
void registerSelf(const SP<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 renderRect(CHyprColor color);
|
||||
|
||||
void onReloadTimerUpdate();
|
||||
void onCrossFadeTimerUpdate();
|
||||
void plantReloadTimer();
|
||||
void startCrossFadeOrUpdateRender();
|
||||
|
||||
private:
|
||||
WP<CBackground> m_self;
|
||||
|
||||
// if needed
|
||||
CFramebuffer blurredFB;
|
||||
|
||||
|
@ -29,9 +55,27 @@ class CBackground : public IWidget {
|
|||
float vibrancy = 0.1696;
|
||||
float vibrancy_darkness = 0.0;
|
||||
Vector2D viewport;
|
||||
std::string path = "";
|
||||
|
||||
std::string outputPort;
|
||||
Hyprutils::Math::eTransform transform;
|
||||
|
||||
std::string resourceID;
|
||||
CColor color;
|
||||
std::string pendingResourceID;
|
||||
|
||||
float crossFadeTime = -1.0;
|
||||
|
||||
CHyprColor color;
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
COutput* output = nullptr;
|
||||
bool isScreenshot = false;
|
||||
SPreloadedAsset* pendingAsset = nullptr;
|
||||
bool firstRender = true;
|
||||
|
||||
UP<SFade> fade;
|
||||
|
||||
int reloadTime = -1;
|
||||
std::string reloadCommand;
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
std::shared_ptr<CTimer> reloadTimer;
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
#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;
|
||||
|
@ -14,16 +16,24 @@ using namespace Hyprutils::String;
|
|||
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 - size.rotated(ang)) / 2.0;
|
||||
rot = (size - rotateVector(size, ang)) / 2.0;
|
||||
|
||||
Vector2D pos = offset;
|
||||
if (halign == "center")
|
||||
|
@ -47,19 +57,28 @@ Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size,
|
|||
return pos;
|
||||
}
|
||||
|
||||
static void replaceAll(std::string& str, const std::string& from, const std::string& to) {
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(from, pos)) != std::string::npos) {
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
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_pHyprlock->getPasswordFailedAttempts();
|
||||
const size_t ATTEMPTS = g_pAuth->getFailedAttempts();
|
||||
const std::string STR = std::to_string(ATTEMPTS);
|
||||
size_t pos = 0;
|
||||
|
||||
|
@ -81,35 +100,78 @@ static void replaceAllAttempts(std::string& str) {
|
|||
}
|
||||
|
||||
static void replaceAllLayout(std::string& str) {
|
||||
|
||||
std::string layoutName = "error";
|
||||
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
|
||||
const auto LAYOUTNAME = xkb_keymap_layout_get_name(g_pHyprlock->m_pXKBKeymap, LAYOUTIDX);
|
||||
const std::string STR = LAYOUTNAME ? LAYOUTNAME : "error";
|
||||
size_t pos = 0;
|
||||
|
||||
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);
|
||||
const std::string LANG = LANGS[LAYOUTIDX].empty() ? STR : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
|
||||
if (LAYOUTIDX >= LANGS.size()) {
|
||||
Debug::log(ERR, "Layout index {} out of bounds. Max is {}.", LAYOUTIDX, LANGS.size() - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
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, STR);
|
||||
pos += STR.length();
|
||||
str.replace(pos, 7, layoutName);
|
||||
pos += layoutName.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getTime() {
|
||||
const auto current_zone = std::chrono::current_zone();
|
||||
const auto HHMMSS = std::chrono::hh_mm_ss{current_zone->to_local(std::chrono::system_clock::now()) -
|
||||
std::chrono::floor<std::chrono::days>(current_zone->to_local(std::chrono::system_clock::now()))};
|
||||
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());
|
||||
|
@ -123,25 +185,18 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
|
|||
Debug::log(WARN, "Error in formatString, user_gecos null. Errno: ", errno);
|
||||
|
||||
IWidget::SFormatResult result;
|
||||
replaceAll(in, "$DESC", std::string{user_gecos ? user_gecos : ""});
|
||||
replaceAll(in, "$USER", std::string{username ? username : ""});
|
||||
replaceAll(in, "<br/>", std::string{"\n"});
|
||||
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("$TIME")) {
|
||||
replaceAll(in, "$TIME", getTime());
|
||||
if (in.contains("$TIME12")) {
|
||||
replaceInString(in, "$TIME12", getTime12h());
|
||||
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
|
||||
}
|
||||
|
||||
if (in.contains("$FAIL")) {
|
||||
const auto FAIL = g_pAuth->getLastFailText();
|
||||
replaceAll(in, "$FAIL", FAIL.has_value() ? FAIL.value() : "");
|
||||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.contains("$PROMPT")) {
|
||||
const auto PROMPT = g_pAuth->getLastPrompt();
|
||||
replaceAll(in, "$PROMPT", PROMPT.has_value() ? PROMPT.value() : "");
|
||||
result.allowForceUpdate = true;
|
||||
if (in.contains("$TIME")) {
|
||||
replaceInString(in, "$TIME", getTime24h());
|
||||
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
|
||||
}
|
||||
|
||||
if (in.contains("$ATTEMPTS")) {
|
||||
|
@ -154,6 +209,36 @@ IWidget::SFormatResult IWidget::formatString(std::string 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);
|
||||
|
@ -181,3 +266,15 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#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;
|
||||
|
||||
virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
|
||||
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;
|
||||
|
@ -23,5 +39,11 @@ class IWidget {
|
|||
bool allowForceUpdate = false;
|
||||
};
|
||||
|
||||
virtual SFormatResult formatString(std::string in);
|
||||
static SFormatResult formatString(std::string in);
|
||||
|
||||
void setHover(bool hover);
|
||||
bool isHovered() const;
|
||||
|
||||
private:
|
||||
bool hovered = false;
|
||||
};
|
||||
|
|
|
@ -2,32 +2,37 @@
|
|||
#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() {
|
||||
if (imageTimer) {
|
||||
imageTimer->cancel();
|
||||
imageTimer.reset();
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PIMAGE = (CImage*)data;
|
||||
void CImage::registerSelf(const SP<CImage>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
static void onTimer(WP<CImage> ref) {
|
||||
if (auto PIMAGE = ref.lock(); PIMAGE) {
|
||||
PIMAGE->onTimerUpdate();
|
||||
PIMAGE->plantTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onAssetCallback(void* data) {
|
||||
const auto PIMAGE = (CImage*)data;
|
||||
PIMAGE->renderSuper();
|
||||
static void onAssetCallback(WP<CImage> ref) {
|
||||
if (auto PIMAGE = ref.lock(); PIMAGE)
|
||||
PIMAGE->renderUpdate();
|
||||
}
|
||||
|
||||
void CImage::onTimerUpdate() {
|
||||
const std::string OLDPATH = path;
|
||||
|
||||
if (!reloadCommand.empty()) {
|
||||
path = g_pHyprlock->spawnSync(reloadCommand);
|
||||
path = spawnSync(reloadCommand);
|
||||
|
||||
if (path.ends_with('\n'))
|
||||
path.pop_back();
|
||||
|
@ -40,7 +45,7 @@ void CImage::onTimerUpdate() {
|
|||
}
|
||||
|
||||
try {
|
||||
const auto MTIME = std::filesystem::last_write_time(path);
|
||||
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
|
||||
if (OLDPATH == path && MTIME == modificationTime)
|
||||
return;
|
||||
|
||||
|
@ -58,9 +63,7 @@ void CImage::onTimerUpdate() {
|
|||
pendingResourceID = request.id;
|
||||
request.asset = path;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
|
||||
|
||||
request.callback = onAssetCallback;
|
||||
request.callbackData = this;
|
||||
request.callback = [REF = m_self]() { onAssetCallback(REF); };
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
}
|
||||
|
@ -68,19 +71,25 @@ void CImage::onTimerUpdate() {
|
|||
void CImage::plantTimer() {
|
||||
|
||||
if (reloadTime == 0) {
|
||||
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
|
||||
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), onTimer, this, false);
|
||||
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, false);
|
||||
}
|
||||
|
||||
CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) :
|
||||
viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) {
|
||||
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.lock(), 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 = std::any_cast<Hyprlang::INT>(props.at("border_color"));
|
||||
pos = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
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"));
|
||||
|
@ -88,34 +97,45 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r
|
|||
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()); //
|
||||
}
|
||||
|
||||
try {
|
||||
modificationTime = std::filesystem::last_write_time(path);
|
||||
} catch (std::exception& e) { Debug::log(ERR, "{}", 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();
|
||||
}
|
||||
}
|
||||
|
||||
bool CImage::draw(const SRenderData& data) {
|
||||
void CImage::reset() {
|
||||
if (imageTimer) {
|
||||
imageTimer->cancel();
|
||||
imageTimer.reset();
|
||||
}
|
||||
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
return;
|
||||
|
||||
if (!pendingResourceID.empty()) {
|
||||
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
|
||||
if (newAsset) {
|
||||
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
|
||||
} else if (resourceID != pendingResourceID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
imageFB.release();
|
||||
|
||||
asset = newAsset;
|
||||
resourceID = pendingResourceID;
|
||||
firstRender = true;
|
||||
}
|
||||
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;
|
||||
|
@ -146,14 +166,14 @@ bool CImage::draw(const SRenderData& data) {
|
|||
texbox.w *= std::max(SCALEX, SCALEY);
|
||||
texbox.h *= std::max(SCALEX, SCALEY);
|
||||
|
||||
const bool ALLOWROUND = rounding > -1 && rounding < std::min(texbox.w, texbox.h) / 2.0;
|
||||
|
||||
// plus borders if any
|
||||
CBox borderBox = {angle == 0 ? BORDERPOS : BORDERPOS + Vector2D{1.0, 1.0}, texbox.size() + IMAGEPOS * 2.0};
|
||||
|
||||
borderBox.round();
|
||||
|
||||
const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0};
|
||||
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);
|
||||
|
@ -161,10 +181,10 @@ bool CImage::draw(const SRenderData& data) {
|
|||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (border > 0)
|
||||
g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : std::min(borderBox.w, borderBox.h) / 2.0);
|
||||
g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0);
|
||||
|
||||
texbox.round();
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
g_pRenderer->popFb();
|
||||
}
|
||||
|
||||
|
@ -178,26 +198,60 @@ bool CImage::draw(const SRenderData& data) {
|
|||
|
||||
shadow.draw(data);
|
||||
|
||||
const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle);
|
||||
pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle);
|
||||
|
||||
texbox.x = TEXPOS.x;
|
||||
texbox.y = TEXPOS.y;
|
||||
texbox.x = pos.x;
|
||||
texbox.y = pos.y;
|
||||
|
||||
texbox.round();
|
||||
texbox.rot = angle;
|
||||
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
|
||||
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
||||
|
||||
return data.opacity < 1.0;
|
||||
}
|
||||
|
||||
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PIMAGE = (CImage*)data;
|
||||
PIMAGE->renderSuper();
|
||||
void CImage::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.release();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void CImage::renderSuper() {
|
||||
g_pHyprlock->renderOutput(output->stringPort);
|
||||
|
||||
if (!pendingResourceID.empty()) /* did not consume the pending resource */
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "../AsyncResourceGatherer.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
|
@ -16,24 +17,35 @@ class COutput;
|
|||
|
||||
class CImage : public IWidget {
|
||||
public:
|
||||
CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props);
|
||||
CImage() = default;
|
||||
~CImage();
|
||||
|
||||
virtual bool draw(const SRenderData& data);
|
||||
void registerSelf(const SP<CImage>& self);
|
||||
|
||||
void renderSuper();
|
||||
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:
|
||||
WP<CImage> m_self;
|
||||
|
||||
CFramebuffer imageFB;
|
||||
|
||||
int size;
|
||||
int rounding;
|
||||
double border;
|
||||
double angle;
|
||||
CColor color;
|
||||
CGradientValueData color;
|
||||
Vector2D pos;
|
||||
Vector2D configPos;
|
||||
|
||||
std::string halign, valign, path;
|
||||
|
||||
|
@ -41,14 +53,17 @@ class CImage : public IWidget {
|
|||
|
||||
int reloadTime;
|
||||
std::string reloadCommand;
|
||||
std::string onclickCommand;
|
||||
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
std::shared_ptr<CTimer> imageTimer;
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
|
||||
Vector2D viewport;
|
||||
std::string stringPort;
|
||||
|
||||
std::string resourceID;
|
||||
std::string pendingResourceID; // if reloading image
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
COutput* output = nullptr;
|
||||
CShadowable shadow;
|
||||
};
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
#include "Label.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include <hyprlang.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() {
|
||||
if (labelTimer) {
|
||||
labelTimer->cancel();
|
||||
labelTimer.reset();
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PLABEL = (CLabel*)data;
|
||||
void CLabel::registerSelf(const SP<CLabel>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
static void onTimer(WP<CLabel> ref) {
|
||||
if (auto PLABEL = ref.lock(); PLABEL) {
|
||||
// update label
|
||||
PLABEL->onTimerUpdate();
|
||||
|
||||
// plant new timer
|
||||
PLABEL->plantTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onAssetCallback(void* data) {
|
||||
const auto PLABEL = (CLabel*)data;
|
||||
PLABEL->renderSuper();
|
||||
static void onAssetCallback(WP<CLabel> ref) {
|
||||
if (auto PLABEL = ref.lock(); PLABEL)
|
||||
PLABEL->renderUpdate();
|
||||
}
|
||||
|
||||
std::string CLabel::getUniqueResourceId() {
|
||||
|
@ -49,25 +52,39 @@ void CLabel::onTimerUpdate() {
|
|||
pendingResourceID = request.id;
|
||||
request.asset = label.formatted;
|
||||
|
||||
request.callback = onAssetCallback;
|
||||
request.callbackData = this;
|
||||
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), onTimer, this, label.allowForceUpdate);
|
||||
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), onTimer, this, true);
|
||||
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, this, true);
|
||||
}
|
||||
|
||||
CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
|
||||
outputStringPort(output), shadow(this, props, viewport_) {
|
||||
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.lock(), 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"));
|
||||
CColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
CHyprColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
|
||||
|
||||
label = formatString(labelPreFormat);
|
||||
|
@ -84,45 +101,48 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string,
|
|||
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);
|
||||
|
||||
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
pos = {POS__.x, POS__.y};
|
||||
configPos = pos;
|
||||
|
||||
viewport = viewport_;
|
||||
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
|
||||
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
|
||||
angle = angle * M_PI / 180.0;
|
||||
|
||||
plantTimer();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
shadow.markShadowDirty();
|
||||
}
|
||||
|
||||
if (!pendingResourceID.empty()) {
|
||||
// new asset is pending
|
||||
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
|
||||
if (newAsset) {
|
||||
// new asset is ready :D
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
asset = newAsset;
|
||||
resourceID = pendingResourceID;
|
||||
pendingResourceID = "";
|
||||
if (updateShadow) {
|
||||
updateShadow = false;
|
||||
shadow.markShadowDirty();
|
||||
}
|
||||
}
|
||||
|
||||
shadow.draw(data);
|
||||
|
||||
|
@ -136,11 +156,41 @@ bool CLabel::draw(const SRenderData& data) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PLABEL = (CLabel*)data;
|
||||
PLABEL->renderSuper();
|
||||
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;
|
||||
}
|
||||
|
||||
void CLabel::renderSuper() {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "IWidget.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "../AsyncResourceGatherer.hpp"
|
||||
#include <string>
|
||||
|
@ -14,16 +14,26 @@ class CSessionLockSurface;
|
|||
|
||||
class CLabel : public IWidget {
|
||||
public:
|
||||
CLabel(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output);
|
||||
CLabel() = default;
|
||||
~CLabel();
|
||||
|
||||
virtual bool draw(const SRenderData& data);
|
||||
void registerSelf(const SP<CLabel>& self);
|
||||
|
||||
void renderSuper();
|
||||
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:
|
||||
WP<CLabel> m_self;
|
||||
|
||||
std::string getUniqueResourceId();
|
||||
|
||||
std::string labelPreFormat;
|
||||
|
@ -36,13 +46,15 @@ class CLabel : public IWidget {
|
|||
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;
|
||||
|
||||
std::shared_ptr<CTimer> labelTimer;
|
||||
std::shared_ptr<CTimer> labelTimer = nullptr;
|
||||
|
||||
CShadowable shadow;
|
||||
bool updateShadow = true;
|
||||
};
|
||||
|
|
|
@ -1,88 +1,127 @@
|
|||
#include "PasswordInputField.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "src/core/Auth.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>
|
||||
|
||||
static void replaceAll(std::string& str, const std::string& from, const std::string& to) {
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(from, pos)) != std::string::npos) {
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
CPasswordInputField::~CPasswordInputField() {
|
||||
reset();
|
||||
}
|
||||
|
||||
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
|
||||
outputStringPort(output), shadow(this, props, viewport_) {
|
||||
size = std::any_cast<Hyprlang::VEC2>(props.at("size"));
|
||||
void CPasswordInputField::registerSelf(const SP<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.lock(), 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"));
|
||||
col.transitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition"));
|
||||
col.outer = std::any_cast<Hyprlang::INT>(props.at("outer_color"));
|
||||
col.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
|
||||
col.font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
|
||||
col.fail = std::any_cast<Hyprlang::INT>(props.at("fail_color"));
|
||||
col.check = std::any_cast<Hyprlang::INT>(props.at("check_color"));
|
||||
col.both = std::any_cast<Hyprlang::INT>(props.at("bothlock_color"));
|
||||
col.caps = std::any_cast<Hyprlang::INT>(props.at("capslock_color"));
|
||||
col.num = std::any_cast<Hyprlang::INT>(props.at("numlock_color"));
|
||||
col.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
|
||||
col.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
|
||||
viewport = viewport_;
|
||||
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()); //
|
||||
}
|
||||
|
||||
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
pos = {POS__.x, POS__.y};
|
||||
configPos = pos;
|
||||
configSize = size;
|
||||
colorState.font = colorConfig.font;
|
||||
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
|
||||
pos = posFromHVAlign(viewport, size, pos, halign, valign);
|
||||
pos = posFromHVAlign(viewport, configSize, pos, halign, valign);
|
||||
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
|
||||
dots.spacing = std::clamp(dots.spacing, 0.f, 1.f);
|
||||
col.transitionMs = std::clamp(col.transitionMs, 0, 1000);
|
||||
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
|
||||
|
||||
col.both = col.both == -1 ? col.outer : col.both;
|
||||
col.caps = col.caps == -1 ? col.outer : col.caps;
|
||||
col.num = col.num == -1 ? col.outer : col.num;
|
||||
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
|
||||
|
||||
g_pHyprlock->m_bNumLock = col.invertNum;
|
||||
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"));
|
||||
|
||||
// Render placeholder if either placeholder_text or fail_text are non-empty
|
||||
// as placeholder must be rendered to show fail_text
|
||||
if (!configPlaceholderText.empty() || !configFailText.empty()) {
|
||||
placeholder.currentText = configPlaceholderText;
|
||||
srand(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
replaceAll(placeholder.currentText, "$PROMPT", "");
|
||||
pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign);
|
||||
|
||||
placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this);
|
||||
if (!dots.textFormat.empty()) {
|
||||
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
request.id = placeholder.resourceID;
|
||||
request.asset = placeholder.currentText;
|
||||
request.id = dots.textResourceID;
|
||||
request.asset = dots.textFormat;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = std::string{"Sans"};
|
||||
request.props["color"] = CColor{1.0 - col.font.r, 1.0 - col.font.g, 1.0 - col.font.b, 0.5};
|
||||
request.props["font_size"] = (int)size.y / 4;
|
||||
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();
|
||||
}
|
||||
|
||||
static void fadeOutCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
CPasswordInputField* p = (CPasswordInputField*)data;
|
||||
void CPasswordInputField::reset() {
|
||||
if (fade.fadeOutTimer.get()) {
|
||||
fade.fadeOutTimer->cancel();
|
||||
fade.fadeOutTimer.reset();
|
||||
}
|
||||
|
||||
p->onFadeOutTimer();
|
||||
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(WP<CPasswordInputField> ref) {
|
||||
if (const auto PP = ref.lock(); PP)
|
||||
PP->onFadeOutTimer();
|
||||
}
|
||||
|
||||
void CPasswordInputField::onFadeOutTimer() {
|
||||
|
@ -94,7 +133,7 @@ void CPasswordInputField::onFadeOutTimer() {
|
|||
|
||||
void CPasswordInputField::updateFade() {
|
||||
if (!fadeOnEmpty) {
|
||||
fade.a = 1.0;
|
||||
fade.a->setValueAndWarp(1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -108,67 +147,34 @@ void CPasswordInputField::updateFade() {
|
|||
fade.fadeOutTimer.reset();
|
||||
}
|
||||
|
||||
if (!INPUTUSED && fade.a != 0.0 && (!fade.animated || fade.appearing)) {
|
||||
if (!INPUTUSED && fade.a->goal() != 0.0) {
|
||||
if (fade.allowFadeOut || fadeTimeoutMs == 0) {
|
||||
fade.a = 1.0;
|
||||
fade.animated = true;
|
||||
fade.appearing = false;
|
||||
fade.start = std::chrono::system_clock::now();
|
||||
*fade.a = 0.0;
|
||||
fade.allowFadeOut = false;
|
||||
} else if (!fade.fadeOutTimer.get())
|
||||
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), fadeOutCallback, this);
|
||||
}
|
||||
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr);
|
||||
|
||||
if (INPUTUSED && fade.a != 1.0 && (!fade.animated || !fade.appearing)) {
|
||||
fade.a = 0.0;
|
||||
fade.animated = true;
|
||||
fade.appearing = true;
|
||||
fade.start = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
if (fade.animated) {
|
||||
if (fade.appearing)
|
||||
fade.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
|
||||
else
|
||||
fade.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
|
||||
|
||||
if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0))
|
||||
fade.animated = false;
|
||||
} else if (INPUTUSED && fade.a->goal() != 1.0)
|
||||
*fade.a = 1.0;
|
||||
|
||||
if (fade.a->isBeingAnimated())
|
||||
redrawShadow = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateDots() {
|
||||
if (passwordLength == dots.currentAmount)
|
||||
if (dots.currentAmount->goal() == passwordLength)
|
||||
return;
|
||||
|
||||
if (std::abs(passwordLength - dots.currentAmount) > 1) {
|
||||
dots.currentAmount = std::clamp(dots.currentAmount, passwordLength - 1.f, passwordLength + 1.f);
|
||||
dots.lastFrame = std::chrono::system_clock::now();
|
||||
}
|
||||
if (checkWaiting)
|
||||
return;
|
||||
|
||||
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000);
|
||||
|
||||
const float TOADD = DELTA / 1000000.0 * dots.speedPerSecond;
|
||||
|
||||
if (passwordLength > dots.currentAmount) {
|
||||
dots.currentAmount += TOADD;
|
||||
if (dots.currentAmount > passwordLength)
|
||||
dots.currentAmount = passwordLength;
|
||||
} else if (passwordLength < dots.currentAmount) {
|
||||
dots.currentAmount -= TOADD;
|
||||
if (dots.currentAmount < passwordLength)
|
||||
dots.currentAmount = passwordLength;
|
||||
}
|
||||
|
||||
dots.lastFrame = std::chrono::system_clock::now();
|
||||
if (passwordLength == 0)
|
||||
dots.currentAmount->setValueAndWarp(passwordLength);
|
||||
else
|
||||
*dots.currentAmount = passwordLength;
|
||||
}
|
||||
|
||||
bool CPasswordInputField::draw(const SRenderData& data) {
|
||||
CBox inputFieldBox = {pos, size};
|
||||
CBox outerBox = {pos - Vector2D{outThick, outThick}, size + Vector2D{outThick * 2, outThick * 2}};
|
||||
|
||||
if (firstRender || redrawShadow) {
|
||||
firstRender = false;
|
||||
redrawShadow = false;
|
||||
|
@ -179,52 +185,38 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
|
||||
passwordLength = g_pHyprlock->getPasswordBufferDisplayLen();
|
||||
checkWaiting = g_pAuth->checkWaiting();
|
||||
displayFail = g_pAuth->m_bDisplayFailText;
|
||||
|
||||
updateFade();
|
||||
updateDots();
|
||||
updatePlaceholder();
|
||||
updateColors();
|
||||
updatePlaceholder();
|
||||
updateWidth();
|
||||
updateHiddenInputState();
|
||||
|
||||
static auto TIMER = std::chrono::system_clock::now();
|
||||
|
||||
if (placeholder.asset) {
|
||||
const auto TARGETSIZEX = placeholder.asset->texture.m_vSize.x + inputFieldBox.h;
|
||||
|
||||
if (size.x < TARGETSIZEX) {
|
||||
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - TIMER).count(), 8000, 20000);
|
||||
TIMER = std::chrono::system_clock::now();
|
||||
forceReload = true;
|
||||
|
||||
size.x += std::clamp((TARGETSIZEX - size.x) * DELTA / 100000.0, 1.0, 1000.0);
|
||||
|
||||
if (size.x > TARGETSIZEX) {
|
||||
size.x = TARGETSIZEX;
|
||||
redrawShadow = true;
|
||||
}
|
||||
}
|
||||
|
||||
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
|
||||
} else if (size.x != configSize.x) {
|
||||
size.x = configSize.x;
|
||||
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
|
||||
}
|
||||
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;
|
||||
shadowData.opacity *= fade.a->value();
|
||||
|
||||
if (!size->isBeingAnimated())
|
||||
shadow.draw(shadowData);
|
||||
|
||||
CColor outerCol = col.outer;
|
||||
outerCol.a *= fade.a * data.opacity;
|
||||
CColor innerCol = col.inner;
|
||||
innerCol.a *= fade.a * data.opacity;
|
||||
CColor fontCol = col.font;
|
||||
fontCol.a *= fade.a * data.opacity;
|
||||
//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) {
|
||||
g_pRenderer->renderRect(outerBox, outerCol, rounding == -1 ? outerBox.h / 2.0 : rounding);
|
||||
const auto OUTERROUND = roundingForBorderBox(outerBox, rounding, outThick);
|
||||
g_pRenderer->renderBorder(outerBox, colorState.outer->value(), outThick, OUTERROUND, fade.a->value() * data.opacity);
|
||||
|
||||
if (passwordLength != 0 && hiddenInputState.enabled && !fade.animated && data.opacity == 1.0) {
|
||||
if (passwordLength != 0 && !checkWaiting && hiddenInputState.enabled) {
|
||||
CBox outerBoxScaled = outerBox;
|
||||
Vector2D p = outerBox.pos();
|
||||
outerBoxScaled.translate(-p).scale(0.5).translate(p);
|
||||
|
@ -234,57 +226,82 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
outerBoxScaled.x += outerBoxScaled.w;
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h);
|
||||
g_pRenderer->renderRect(outerBox, hiddenInputState.lastColor, rounding == -1 ? outerBox.h / 2.0 : rounding);
|
||||
g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity);
|
||||
glScissor(0, 0, viewport.x, viewport.y);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
g_pRenderer->renderRect(inputFieldBox, innerCol, rounding == -1 ? inputFieldBox.h / 2.0 : rounding - outThick);
|
||||
const int ROUND = roundingForBox(inputFieldBox, rounding);
|
||||
g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND);
|
||||
|
||||
if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) {
|
||||
const int PASS_SIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f;
|
||||
const int PASS_SPACING = std::floor(PASS_SIZE * dots.spacing);
|
||||
const int DOT_PAD = (inputFieldBox.h - PASS_SIZE) / 2;
|
||||
const int DOT_AREA_WIDTH = inputFieldBox.w - DOT_PAD * 2; // avail width for dots
|
||||
const int MAX_DOTS = std::round(DOT_AREA_WIDTH * 1.0 / (PASS_SIZE + PASS_SPACING)); // max amount of dots that can fit in the area
|
||||
const int DOT_FLOORED = std::floor(dots.currentAmount);
|
||||
const float DOT_ALPHA = fontCol.a;
|
||||
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 int TOTAL_DOTS_WIDTH = (PASS_SIZE + PASS_SPACING) * dots.currentAmount - PASS_SPACING;
|
||||
const double CURRWIDTH = ((passSize.x + passSpacing) * CURRDOTS) - passSpacing;
|
||||
|
||||
// Calculate starting x-position to ensure dots stay centered within the input field
|
||||
int xstart = dots.center ? (DOT_AREA_WIDTH - TOTAL_DOTS_WIDTH) / 2 + DOT_PAD : DOT_PAD;
|
||||
double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD : DOTPAD;
|
||||
|
||||
if (dots.currentAmount > MAX_DOTS)
|
||||
xstart = (inputFieldBox.w + MAX_DOTS * (PASS_SIZE + PASS_SPACING) - PASS_SPACING - 2 * TOTAL_DOTS_WIDTH) / 2;
|
||||
if (CURRDOTS > MAXDOTS)
|
||||
xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2.0;
|
||||
|
||||
if (dots.rounding == -1)
|
||||
dots.rounding = PASS_SIZE / 2.0;
|
||||
dots.rounding = passSize.x / 2.0;
|
||||
else if (dots.rounding == -2)
|
||||
dots.rounding = rounding == -1 ? PASS_SIZE / 2.0 : rounding * dots.size;
|
||||
dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size;
|
||||
|
||||
for (int i = 0; i < dots.currentAmount; ++i) {
|
||||
if (i < DOT_FLOORED - MAX_DOTS)
|
||||
for (int i = 0; i < CURRDOTS; ++i) {
|
||||
if (i < DOTFLOORED - MAXDOTS)
|
||||
continue;
|
||||
|
||||
if (dots.currentAmount != DOT_FLOORED) {
|
||||
if (i == DOT_FLOORED)
|
||||
fontCol.a *= (dots.currentAmount - DOT_FLOORED) * data.opacity;
|
||||
else if (i == DOT_FLOORED - MAX_DOTS)
|
||||
fontCol.a *= (1 - dots.currentAmount + DOT_FLOORED) * data.opacity;
|
||||
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 + (int)inputFieldBox.w % 2 / 2.f + i * (PASS_SIZE + PASS_SPACING), inputFieldBox.h / 2.f - PASS_SIZE / 2.f};
|
||||
CBox box{dotPosition, Vector2D{PASS_SIZE, PASS_SIZE}};
|
||||
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 = DOT_ALPHA;
|
||||
|
||||
fontCol.a = DOTALPHA;
|
||||
}
|
||||
}
|
||||
|
||||
if (passwordLength == 0 && !placeholder.resourceID.empty()) {
|
||||
if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) {
|
||||
SPreloadedAsset* currAsset = nullptr;
|
||||
|
||||
if (!placeholder.asset)
|
||||
|
@ -293,20 +310,25 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
currAsset = placeholder.asset;
|
||||
|
||||
if (currAsset) {
|
||||
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
|
||||
pos = pos - currAsset->texture.m_vSize / 2.f;
|
||||
CBox textbox{pos, currAsset->texture.m_vSize};
|
||||
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
|
||||
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 dots.currentAmount != passwordLength || fade.animated || col.animated || redrawShadow || data.opacity < 1.0 || forceReload;
|
||||
return redrawShadow || forceReload;
|
||||
}
|
||||
|
||||
void CPasswordInputField::updatePlaceholder() {
|
||||
if (passwordLength != 0) {
|
||||
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ placeholder.isFailText) {
|
||||
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;
|
||||
|
@ -316,30 +338,34 @@ void CPasswordInputField::updatePlaceholder() {
|
|||
return;
|
||||
}
|
||||
|
||||
const auto AUTHFEEDBACK = g_pAuth->m_bDisplayFailText ? g_pAuth->getLastFailText().value_or("Ups, no fail text?") : g_pAuth->getLastPrompt().value_or("Ups, no prompt?");
|
||||
|
||||
if (placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts)
|
||||
// already requested a placeholder for the current fail
|
||||
if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts())
|
||||
return;
|
||||
|
||||
placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts();
|
||||
placeholder.isFailText = g_pAuth->m_bDisplayFailText;
|
||||
placeholder.lastAuthFeedback = AUTHFEEDBACK;
|
||||
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:{}{}{}{}{}{}", placeholder.currentText, (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 (placeholder.isFailText) {
|
||||
placeholder.currentText = configFailText;
|
||||
replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK);
|
||||
replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(placeholder.failedAttempts));
|
||||
} else {
|
||||
placeholder.currentText = configPlaceholderText;
|
||||
replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK);
|
||||
}
|
||||
|
||||
placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this);
|
||||
if (std::find(placeholder.registeredResourceIDs.begin(), placeholder.registeredResourceIDs.end(), placeholder.resourceID) != placeholder.registeredResourceIDs.end())
|
||||
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
|
||||
|
@ -347,12 +373,38 @@ void CPasswordInputField::updatePlaceholder() {
|
|||
request.id = placeholder.resourceID;
|
||||
request.asset = placeholder.currentText;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = std::string{"Sans"};
|
||||
request.props["color"] = (placeholder.isFailText) ? col.fail : col.font;
|
||||
request.props["font_size"] = (int)size.y / 4;
|
||||
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;
|
||||
|
@ -360,146 +412,75 @@ void CPasswordInputField::updateHiddenInputState() {
|
|||
// randomize new thang
|
||||
hiddenInputState.lastPasswordLength = passwordLength;
|
||||
|
||||
srand(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
float r1 = (rand() % 100) / 255.0;
|
||||
float r2 = (rand() % 100) / 255.0;
|
||||
int r3 = rand() % 3;
|
||||
int r4 = rand() % 2;
|
||||
int r5 = rand() % 2;
|
||||
const auto BASEOK = colorConfig.hiddenBase.asOkLab();
|
||||
|
||||
((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0;
|
||||
((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0;
|
||||
// convert to polar coordinates
|
||||
const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2));
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (i != r3 && i != ((r3 + r4) % 3)) {
|
||||
((float*)&hiddenInputState.lastColor.r)[i] = 1.0 - ((float*)&hiddenInputState.lastColor.r)[r5 ? r3 : ((r3 + r4) % 3)];
|
||||
}
|
||||
}
|
||||
// now randomly rotate the hue
|
||||
const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4;
|
||||
|
||||
hiddenInputState.lastColor.a = 1.0;
|
||||
// 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;
|
||||
}
|
||||
|
||||
static void changeChannel(const float& source, const float& target, float& subject, const double& multi, bool& animated) {
|
||||
|
||||
const float DELTA = target - source;
|
||||
|
||||
if (subject != target) {
|
||||
subject += DELTA * multi;
|
||||
animated = true;
|
||||
|
||||
if ((source < target && subject > target) || (source > target && subject < target))
|
||||
subject = target;
|
||||
}
|
||||
}
|
||||
|
||||
static void changeColor(const CColor& source, const CColor& target, CColor& subject, const double& multi, bool& animated) {
|
||||
|
||||
changeChannel(source.r, target.r, subject.r, multi, animated);
|
||||
changeChannel(source.g, target.g, subject.g, multi, animated);
|
||||
changeChannel(source.b, target.b, subject.b, multi, animated);
|
||||
changeChannel(source.a, target.a, subject.a, multi, animated);
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateColors() {
|
||||
static auto OUTER = col.outer, TARGET = OUTER, SOURCE = OUTER;
|
||||
static auto INNER = col.inner, ITARGET = INNER, ISOURCE = INNER;
|
||||
static auto FONT = col.font, FTARGET = FONT, FSOURCE = FONT;
|
||||
|
||||
const bool BORDERLESS = outThick == 0;
|
||||
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
|
||||
|
||||
if (col.animated) {
|
||||
// some cases when events happen too quick (within transitionMs)
|
||||
// TODO: find more?
|
||||
const bool LOCKCHANGED = col.stateNum != (col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock) || col.stateCaps != g_pHyprlock->m_bCapsLock;
|
||||
const bool ANIMONCHECK = checkWaiting && (TARGET == (BORDERLESS ? INNER : OUTER) || TARGET == col.fail);
|
||||
CGradientValueData* targetGrad = nullptr;
|
||||
|
||||
if (LOCKCHANGED || ANIMONCHECK) {
|
||||
const bool EQUALCOLORS = ANIMONCHECK && OUTER == col.check;
|
||||
// to avoid throttle when check_color set to the same as outer.
|
||||
SOURCE = BORDERLESS ? (EQUALCOLORS ? INNER : col.inner) : col.outer;
|
||||
FSOURCE = EQUALCOLORS ? FONT : col.font;
|
||||
ISOURCE = EQUALCOLORS ? INNER : col.inner;
|
||||
}
|
||||
} else {
|
||||
SOURCE = BORDERLESS ? col.inner : col.outer;
|
||||
FSOURCE = col.font;
|
||||
ISOURCE = col.inner;
|
||||
}
|
||||
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;
|
||||
|
||||
col.stateNum = col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
|
||||
col.stateCaps = g_pHyprlock->m_bCapsLock;
|
||||
if (checkWaiting)
|
||||
targetGrad = colorConfig.check;
|
||||
else if (displayFail && passwordLength == 0)
|
||||
targetGrad = colorConfig.fail;
|
||||
|
||||
if (!placeholder.isFailText || passwordLength > 0 || (passwordLength == 0 && checkWaiting)) {
|
||||
if (g_pHyprlock->m_bFadeStarted) {
|
||||
if (TARGET == col.check)
|
||||
SOURCE = BORDERLESS ? col.inner : col.outer;
|
||||
col.transitionMs = 100;
|
||||
TARGET = BORDERLESS ? INNER : OUTER;
|
||||
} else if (checkWaiting) {
|
||||
FTARGET = col.swapFont ? INNER : FONT;
|
||||
const float PASSALPHA = FTARGET.a * 0.5;
|
||||
FTARGET.a = PASSALPHA;
|
||||
CGradientValueData* outerTarget = colorConfig.outer;
|
||||
CHyprColor innerTarget = colorConfig.inner;
|
||||
CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
|
||||
|
||||
TARGET = col.check;
|
||||
ITARGET = col.swapFont ? FONT : INNER;
|
||||
} else if (col.stateCaps && col.stateNum && col.both != OUTER) {
|
||||
TARGET = col.both;
|
||||
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
|
||||
} else if (col.stateCaps && col.caps != OUTER) {
|
||||
TARGET = col.caps;
|
||||
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
|
||||
} else if (col.stateNum && col.num != OUTER) {
|
||||
TARGET = col.num;
|
||||
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
|
||||
} else {
|
||||
// if quickly pressed after failure
|
||||
if (col.animated && TARGET == col.fail)
|
||||
SOURCE = BORDERLESS ? col.inner : col.outer;
|
||||
|
||||
TARGET = BORDERLESS ? INNER : OUTER;
|
||||
FTARGET = FONT;
|
||||
ITARGET = INNER;
|
||||
}
|
||||
} else {
|
||||
FSOURCE = col.swapFont ? INNER : FONT;
|
||||
const float PASSALPHA = FSOURCE.a * 0.5;
|
||||
FSOURCE.a = PASSALPHA;
|
||||
FTARGET = FONT;
|
||||
|
||||
SOURCE = col.check;
|
||||
TARGET = col.fail;
|
||||
ISOURCE = FONT;
|
||||
ITARGET = FONT;
|
||||
|
||||
if (fade.animated || fade.a < 1.0) {
|
||||
TARGET = BORDERLESS ? INNER : OUTER;
|
||||
SOURCE = col.fail;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
col.animated = false;
|
||||
if (!BORDERLESS && *outerTarget != colorState.outer->goal())
|
||||
*colorState.outer = *outerTarget;
|
||||
|
||||
const bool SWAPDONE = !BORDERLESS && col.swapFont ? col.inner == ITARGET : true;
|
||||
if (innerTarget != colorState.inner->goal())
|
||||
*colorState.inner = innerTarget;
|
||||
|
||||
if ((BORDERLESS ? col.inner : col.outer) == TARGET && col.font == FTARGET && SWAPDONE) {
|
||||
col.shouldStart = true;
|
||||
return;
|
||||
colorState.font = fontTarget;
|
||||
}
|
||||
|
||||
if (col.shouldStart) {
|
||||
col.lastFrame = std::chrono::system_clock::now();
|
||||
col.shouldStart = false;
|
||||
CBox CPasswordInputField::getBoundingBoxWl() const {
|
||||
return {
|
||||
Vector2D{pos.x, viewport.y - pos.y - size->value().y},
|
||||
size->value(),
|
||||
};
|
||||
}
|
||||
|
||||
const auto MULTI = col.transitionMs == 0 ?
|
||||
1.0 :
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - col.lastFrame).count() / (double)col.transitionMs, 0.016, 0.5);
|
||||
|
||||
changeColor(SOURCE, TARGET, (BORDERLESS ? col.inner : col.outer), MULTI, col.animated);
|
||||
changeColor(FSOURCE, FTARGET, col.font, MULTI, col.animated);
|
||||
if (col.swapFont && !BORDERLESS)
|
||||
changeColor(ISOURCE, ITARGET, col.inner, MULTI, col.animated);
|
||||
|
||||
col.lastFrame = std::chrono::system_clock::now();
|
||||
void CPasswordInputField::onHover(const Vector2D& pos) {
|
||||
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
#include <chrono>
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "../../helpers/AnimatedVariable.hpp"
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <vector>
|
||||
#include <any>
|
||||
#include <unordered_map>
|
||||
|
@ -14,49 +16,62 @@ struct SPreloadedAsset;
|
|||
|
||||
class CPasswordInputField : public IWidget {
|
||||
public:
|
||||
CPasswordInputField(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output);
|
||||
CPasswordInputField() = default;
|
||||
virtual ~CPasswordInputField();
|
||||
|
||||
void registerSelf(const SP<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:
|
||||
WP<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;
|
||||
|
||||
Vector2D size;
|
||||
PHLANIMVAR<Vector2D> size;
|
||||
Vector2D pos;
|
||||
Vector2D viewport;
|
||||
Vector2D configPos;
|
||||
Vector2D configSize;
|
||||
|
||||
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText;
|
||||
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
|
||||
uint64_t configFailTimeoutMs = 2000;
|
||||
|
||||
int outThick, rounding;
|
||||
|
||||
struct {
|
||||
float currentAmount = 0;
|
||||
float speedPerSecond = 5; // actually per... something. I am unsure xD
|
||||
std::chrono::system_clock::time_point lastFrame;
|
||||
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 {
|
||||
std::chrono::system_clock::time_point start;
|
||||
float a = 0;
|
||||
PHLANIMVAR<float> a;
|
||||
bool appearing = true;
|
||||
bool animated = false;
|
||||
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
|
||||
bool allowFadeOut = false;
|
||||
} fade;
|
||||
|
@ -67,43 +82,41 @@ class CPasswordInputField : public IWidget {
|
|||
|
||||
std::string currentText = "";
|
||||
size_t failedAttempts = 0;
|
||||
bool canGetNewText = true;
|
||||
bool isFailText = false;
|
||||
|
||||
std::string lastAuthFeedback;
|
||||
|
||||
std::vector<std::string> registeredResourceIDs;
|
||||
|
||||
} placeholder;
|
||||
|
||||
struct {
|
||||
CColor lastColor;
|
||||
CHyprColor lastColor;
|
||||
int lastQuadrant = 0;
|
||||
int lastPasswordLength = 0;
|
||||
bool enabled = false;
|
||||
} hiddenInputState;
|
||||
|
||||
struct {
|
||||
CColor outer;
|
||||
CColor inner;
|
||||
CColor font;
|
||||
CColor fail;
|
||||
CColor check;
|
||||
CColor caps;
|
||||
CColor num;
|
||||
CColor both;
|
||||
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 animated = false;
|
||||
bool stateNum = false;
|
||||
bool stateCaps = false;
|
||||
bool swapFont = false;
|
||||
bool shouldStart;
|
||||
} colorConfig;
|
||||
|
||||
//
|
||||
std::chrono::system_clock::time_point lastFrame;
|
||||
} col;
|
||||
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;
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#include "Shadowable.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
void CShadowable::configure(WP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) {
|
||||
m_widget = widget_;
|
||||
viewport = viewport_;
|
||||
|
||||
CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) : widget(widget_), viewport(viewport_) {
|
||||
size = std::any_cast<Hyprlang::INT>(props.at("shadow_size"));
|
||||
passes = std::any_cast<Hyprlang::INT>(props.at("shadow_passes"));
|
||||
color = std::any_cast<Hyprlang::INT>(props.at("shadow_color"));
|
||||
|
@ -9,6 +13,10 @@ CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string,
|
|||
}
|
||||
|
||||
void CShadowable::markShadowDirty() {
|
||||
const auto WIDGET = m_widget.lock();
|
||||
|
||||
if (!m_widget)
|
||||
return;
|
||||
|
||||
if (passes == 0)
|
||||
return;
|
||||
|
@ -21,7 +29,7 @@ void CShadowable::markShadowDirty() {
|
|||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ignoreDraw = true;
|
||||
widget->draw(IWidget::SRenderData{.opacity = 1.0});
|
||||
WIDGET->draw(IWidget::SRenderData{.opacity = 1.0});
|
||||
ignoreDraw = false;
|
||||
|
||||
g_pRenderer->blurFB(shadowFB, CRenderer::SBlurParams{.size = size, .passes = passes, .colorize = color, .boostA = boostA});
|
||||
|
@ -30,13 +38,13 @@ void CShadowable::markShadowDirty() {
|
|||
}
|
||||
|
||||
bool CShadowable::draw(const IWidget::SRenderData& data) {
|
||||
if (passes == 0)
|
||||
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, WL_OUTPUT_TRANSFORM_NORMAL);
|
||||
g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
return true;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "../Framebuffer.hpp"
|
||||
#include "../Texture.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "IWidget.hpp"
|
||||
|
||||
#include <string>
|
||||
|
@ -11,18 +11,20 @@
|
|||
|
||||
class CShadowable {
|
||||
public:
|
||||
CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
|
||||
virtual ~CShadowable() = default;
|
||||
CShadowable() = default;
|
||||
void configure(WP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
|
||||
|
||||
// instantly re-renders the shadow using the widget's draw() method
|
||||
void markShadowDirty();
|
||||
virtual bool draw(const IWidget::SRenderData& data);
|
||||
|
||||
private:
|
||||
IWidget* widget = nullptr;
|
||||
WP<IWidget> m_widget;
|
||||
int size = 10;
|
||||
int passes = 4;
|
||||
float boostA = 1.0;
|
||||
CColor color{0, 0, 0, 1.0};
|
||||
CHyprColor color{0, 0, 0, 1.0};
|
||||
Vector2D viewport;
|
||||
|
||||
// to avoid recursive shadows
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
#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>
|
||||
|
||||
CShape::CShape(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) : shadow(this, props, viewport_) {
|
||||
void CShape::registerSelf(const SP<CShape>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
size = std::any_cast<Hyprlang::VEC2>(props.at("size"));
|
||||
void CShape::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
|
||||
viewport = pOutput->getViewport();
|
||||
|
||||
shadow.configure(m_self.lock(), 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"));
|
||||
borderColor = std::any_cast<Hyprlang::INT>(props.at("border_color"));
|
||||
pos = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
||||
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()); //
|
||||
}
|
||||
|
||||
viewport = viewport_;
|
||||
angle = angle * M_PI / 180.0;
|
||||
|
||||
const Vector2D VBORDER = {border, border};
|
||||
|
@ -47,9 +65,7 @@ bool CShape::draw(const SRenderData& data) {
|
|||
if (xray) {
|
||||
if (border > 0) {
|
||||
const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI));
|
||||
CColor borderCol = borderColor;
|
||||
borderCol.a *= data.opacity;
|
||||
g_pRenderer->renderRect(borderBox, borderCol, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND));
|
||||
g_pRenderer->renderBorder(borderBox, borderGrad, border, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND), data.opacity);
|
||||
}
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
@ -62,18 +78,19 @@ bool CShape::draw(const SRenderData& data) {
|
|||
}
|
||||
|
||||
if (!shapeFB.isAllocated()) {
|
||||
const auto MINHALFSHAPE = std::min(shapeBox.w, shapeBox.h) / 2.0;
|
||||
const bool ALLOWROUND = rounding > -1 && rounding < MINHALFSHAPE;
|
||||
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);
|
||||
shapeFB.alloc(borderBox.width + (borderBox.x * 2.0), borderBox.height + (borderBox.y * 2.0), true);
|
||||
g_pRenderer->pushFb(shapeFB.m_iFb);
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (border > 0)
|
||||
g_pRenderer->renderRect(borderBox, borderColor, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER);
|
||||
g_pRenderer->renderBorder(borderBox, borderGrad, border, BORDERROUND, 1.0);
|
||||
|
||||
g_pRenderer->renderRect(shapeBox, color, ALLOWROUND ? rounding : MINHALFSHAPE);
|
||||
g_pRenderer->renderRect(shapeBox, color, ROUND);
|
||||
g_pRenderer->popFb();
|
||||
}
|
||||
|
||||
|
@ -83,7 +100,23 @@ bool CShape::draw(const SRenderData& data) {
|
|||
texbox.round();
|
||||
texbox.rot = angle;
|
||||
|
||||
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../helpers/Vector2D.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Box.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(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props);
|
||||
CShape() = default;
|
||||
virtual ~CShape() = default;
|
||||
|
||||
void registerSelf(const SP<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:
|
||||
WP<CShape> m_self;
|
||||
|
||||
CFramebuffer shapeFB;
|
||||
|
||||
int rounding;
|
||||
double border;
|
||||
double angle;
|
||||
CColor color;
|
||||
CColor borderColor;
|
||||
CHyprColor color;
|
||||
CGradientValueData borderGrad;
|
||||
Vector2D size;
|
||||
Vector2D pos;
|
||||
CBox shapeBox;
|
||||
|
@ -32,6 +41,7 @@ class CShape : public IWidget {
|
|||
std::string halign, valign;
|
||||
|
||||
bool firstRender = true;
|
||||
std::string onclickCommand;
|
||||
|
||||
Vector2D viewport;
|
||||
CShadowable shadow;
|
||||
|
|
Loading…
Reference in a new issue