mirror of
https://github.com/hyprwm/hyprlock.git
synced 2025-12-20 17:00:03 +01:00
Compare commits
104 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef3017f5ef | ||
|
|
d099f87d36 | ||
|
|
e2da7c6b1f | ||
|
|
98b86752fe | ||
|
|
36ec73f166 | ||
|
|
bdc44ab5d6 | ||
|
|
de2cc5bd54 | ||
|
|
c8a6768dca | ||
|
|
3cb799b184 | ||
| c48279d1e0 | |||
|
|
7f769fa993 | ||
|
|
1380ca04ae | ||
|
|
61b36c64a8 | ||
|
|
a7f2634a9e | ||
|
|
450ae1e5f0 | ||
|
|
04cfdc4e5b | ||
|
|
cedbb24472 | ||
|
|
8d0e56998e | ||
|
|
a356bf055b | ||
|
|
347e05a40e | ||
| bdf0ef8282 | |||
|
|
71691634e4 | ||
|
|
1e5e62d6e3 | ||
|
|
d993bdc105 | ||
|
|
8ebcee2969 | ||
|
|
1553dd78fc | ||
| 31297a871b | |||
|
|
17fefce86e | ||
|
|
46498da4a0 | ||
|
|
fee04f6406 | ||
|
|
d84b44e695 | ||
|
|
7999f448d7 | ||
|
|
a9638986c3 | ||
| e67036e8cc | |||
|
|
f9d8dfab7a | ||
|
|
1303bb4b7e | ||
|
|
8455fc8ca6 | ||
|
|
da1d076d84 | ||
|
|
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 |
73 changed files with 3923 additions and 2512 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
|
||||
31
.github/workflows/nix.yml
vendored
31
.github/workflows/nix.yml
vendored
|
|
@ -7,8 +7,35 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Install Nix
|
||||
uses: nixbuild/nix-quick-install-action@v31
|
||||
with:
|
||||
nix_conf: |
|
||||
keep-env-derivations = true
|
||||
keep-outputs = true
|
||||
|
||||
- name: Restore and save Nix store
|
||||
uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
# 1G = 1073741824
|
||||
gc-max-store-size-linux: 1G
|
||||
# do purge caches
|
||||
purge: true
|
||||
# purge all versions of the cache
|
||||
purge-prefixes: nix-${{ runner.os }}-
|
||||
# created more than this number of seconds ago
|
||||
purge-created: 0
|
||||
# or, last accessed more than this number of seconds ago
|
||||
# relative to the start of the `Post Restore and save Nix store` phase
|
||||
purge-last-accessed: 0
|
||||
# except any version with the key that is the same as the `primary-key`
|
||||
purge-primary-key: never
|
||||
|
||||
# not needed (yet)
|
||||
# - uses: cachix/cachix-action@v12
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -11,3 +11,4 @@ compile_commands.json
|
|||
protocols/*.cpp
|
||||
protocols/*.hpp
|
||||
*.kdev4
|
||||
.gdb_history
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
|
||||
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
|
||||
string(STRIP ${VER_RAW} VERSION)
|
||||
|
|
@ -28,25 +28,39 @@ set(CMAKE_CXX_STANDARD 23)
|
|||
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
|
||||
-Wno-missing-field-initializers -Wno-narrowing)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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="${VERSION}")
|
||||
add_compile_definitions(HYPRLOCK_COMMIT="${GIT_SHORT_HASH}")
|
||||
add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${GIT_TAG_SHORT_HASH}")
|
||||
|
||||
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}/=)
|
||||
|
|
@ -56,34 +70,43 @@ message(STATUS "Checking deps...")
|
|||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
|
||||
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
|
||||
pkg_check_modules(
|
||||
deps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
wayland-client
|
||||
wayland-protocols
|
||||
wayland-protocols>=1.35
|
||||
wayland-egl
|
||||
hyprlang>=0.4.0
|
||||
hyprlang>=0.6.0
|
||||
egl
|
||||
opengl
|
||||
xkbcommon
|
||||
libjpeg
|
||||
libwebp
|
||||
libmagic
|
||||
cairo
|
||||
pangocairo
|
||||
libdrm
|
||||
gbm
|
||||
hyprutils>=0.2.6
|
||||
hyprutils>=0.11.0
|
||||
sdbus-c++>=2.0.0
|
||||
hyprgraphics)
|
||||
hyprgraphics>=0.1.6)
|
||||
find_library(PAM_FOUND NAMES pam libpam)
|
||||
if(PAM_FOUND)
|
||||
set(PAM_LIB ${PAM_FOUND})
|
||||
else()
|
||||
pkg_check_modules(PAM IMPORTED_TARGET pam libpam)
|
||||
if(PAM_FOUND)
|
||||
set(PAM_LIB PkgConfig::PAM)
|
||||
else()
|
||||
message(FATAL_ERROR "The required library libpam was not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "Found pam at ${PAM_LIB}")
|
||||
|
||||
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
add_executable(hyprlock ${SRCFILES})
|
||||
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
|
||||
OpenGL::EGL OpenGL::GL)
|
||||
target_link_libraries(hyprlock PRIVATE ${PAM_LIB} rt Threads::Threads PkgConfig::deps
|
||||
OpenGL::EGL OpenGL::GLES3)
|
||||
|
||||
# protocols
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
|
|
|
|||
38
README.md
38
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
|
||||
|
||||
|
|
@ -25,26 +29,22 @@ yay -S hyprlock-git # compiles from latest source
|
|||
|
||||
### Deps
|
||||
You need the following dependencies
|
||||
- wayland-client
|
||||
- wayland-protocols
|
||||
- mesa
|
||||
- hyprwayland-scanner
|
||||
|
||||
And the development libraries for the following
|
||||
- cairo
|
||||
- libdrm
|
||||
- pango
|
||||
- xkbcommon
|
||||
- pam
|
||||
- hyprgraphics
|
||||
- hyprlang
|
||||
- hyprutils
|
||||
- hyprgraphics
|
||||
- libmagic (file-devel on Fedora)
|
||||
- hyprwayland-scanner
|
||||
- mesa (required is libgbm, libdrm and the opengl runtime)
|
||||
- pam
|
||||
- pango
|
||||
- sdbus-cpp (>= 2.0.0)
|
||||
- wayland-client
|
||||
- wayland-protocols
|
||||
- xkbcommon
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.6.0
|
||||
0.9.2
|
||||
|
|
|
|||
|
|
@ -1,11 +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
|
||||
#
|
||||
|
||||
input-field {
|
||||
monitor =
|
||||
fade_on_empty = false
|
||||
$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 {
|
||||
color = rgb(23, 39, 41)
|
||||
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
|
||||
}
|
||||
|
|
|
|||
30
flake.lock
generated
30
flake.lock
generated
|
|
@ -13,11 +13,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1734906236,
|
||||
"narHash": "sha256-vH/ysV2ONGQgYZPtcJKwc8jJivzyVxru2aaOxC20ZOE=",
|
||||
"lastModified": 1763733840,
|
||||
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "6dea3fba08fd704dd624b6d4b261638fb4003c9c",
|
||||
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -39,11 +39,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735393019,
|
||||
"narHash": "sha256-NPpqA8rtmDLsEmZOmz+qR67zsB6Y503Jnv+nSFLKJZ8=",
|
||||
"lastModified": 1764612430,
|
||||
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "55608efdaa387af7bfdc0eddb404c409958efa43",
|
||||
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -62,11 +62,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735316583,
|
||||
"narHash": "sha256-AiiUwHWHfEdpFzXy7l1x3zInCUa1xcRMrbZ1XRSkzwU=",
|
||||
"lastModified": 1766160771,
|
||||
"narHash": "sha256-roINUGikWRqqgKrD4iotKbGj3ZKJl3hjMz5l/SyKrHw=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "8f15d45b120b33712f6db477fe5ffb18034d0ea8",
|
||||
"rev": "5ac060bfcf2f12b3a6381156ebbc13826a05b09f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -85,11 +85,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735493474,
|
||||
"narHash": "sha256-fktzv4NaqKm94VAkAoVqO/nqQlw+X0/tJJNAeCSfzK4=",
|
||||
"lastModified": 1763640274,
|
||||
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"rev": "de913476b59ee88685fdc018e77b8f6637a2ae0b",
|
||||
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -100,11 +100,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1735291276,
|
||||
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
|
||||
"lastModified": 1766070988,
|
||||
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
|
||||
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,17 @@
|
|||
cmake,
|
||||
pkg-config,
|
||||
cairo,
|
||||
file,
|
||||
libdrm,
|
||||
libGL,
|
||||
libjpeg,
|
||||
libwebp,
|
||||
libxkbcommon,
|
||||
mesa,
|
||||
libgbm,
|
||||
hyprgraphics,
|
||||
hyprlang,
|
||||
hyprutils,
|
||||
hyprwayland-scanner,
|
||||
pam,
|
||||
pango,
|
||||
sdbus-cpp,
|
||||
sdbus-cpp_2,
|
||||
systemdLibs,
|
||||
wayland,
|
||||
wayland-protocols,
|
||||
|
|
@ -40,19 +37,16 @@ stdenv.mkDerivation {
|
|||
|
||||
buildInputs = [
|
||||
cairo
|
||||
file
|
||||
libdrm
|
||||
libGL
|
||||
libjpeg
|
||||
libwebp
|
||||
libxkbcommon
|
||||
mesa
|
||||
libgbm
|
||||
hyprgraphics
|
||||
hyprlang
|
||||
hyprutils
|
||||
pam
|
||||
pango
|
||||
sdbus-cpp
|
||||
sdbus-cpp_2
|
||||
systemdLibs
|
||||
wayland
|
||||
wayland-protocols
|
||||
|
|
@ -60,7 +54,7 @@ stdenv.mkDerivation {
|
|||
|
||||
cmakeFlags = lib.mapAttrsToList lib.cmakeFeature {
|
||||
HYPRLOCK_COMMIT = shortRev;
|
||||
HYPRLOCK_VERSION_COMMIT = shortRev;
|
||||
HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit)
|
||||
};
|
||||
|
||||
meta = {
|
||||
|
|
|
|||
|
|
@ -18,27 +18,13 @@ in {
|
|||
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.gcc14Stdenv;
|
||||
stdenv = prev.gcc15Stdenv;
|
||||
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
|
||||
inherit (final) hyprlang;
|
||||
shortRev = self.sourceInfo.shortRev or "dirty";
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
sdbuscpp = final: prev: {
|
||||
sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: {
|
||||
version = "2.0.0";
|
||||
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "Kistler-group";
|
||||
repo = "sdbus-cpp";
|
||||
rev = "refs/tags/v${self.version}";
|
||||
hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g=";
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
#include <memory>
|
||||
|
||||
CAuth::CAuth() {
|
||||
static auto* const PENABLEPAM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:pam:enabled");
|
||||
if (**PENABLEPAM)
|
||||
m_vImpls.push_back(std::make_shared<CPam>());
|
||||
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:fingerprint:enabled");
|
||||
if (**PENABLEFINGERPRINT)
|
||||
m_vImpls.push_back(std::make_shared<CFingerprint>());
|
||||
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!");
|
||||
}
|
||||
|
|
@ -29,32 +29,16 @@ void CAuth::submitInput(const std::string& input) {
|
|||
for (const auto& i : m_vImpls) {
|
||||
i->handleInput(input);
|
||||
}
|
||||
|
||||
g_pHyprlock->clearPasswordBuffer();
|
||||
}
|
||||
|
||||
bool CAuth::checkWaiting() {
|
||||
for (const auto& i : m_vImpls) {
|
||||
if (i->checkWaiting())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return std::ranges::any_of(m_vImpls, [](const auto& i) { return i->checkWaiting(); });
|
||||
}
|
||||
|
||||
std::string CAuth::getInlineFeedback() {
|
||||
std::optional<std::string> firstFeedback = std::nullopt;
|
||||
for (const auto& i : m_vImpls) {
|
||||
const auto FEEDBACK = (m_bDisplayFailText) ? i->getLastFailText() : i->getLastPrompt();
|
||||
if (!FEEDBACK.has_value())
|
||||
continue;
|
||||
|
||||
if (!firstFeedback.has_value())
|
||||
firstFeedback = FEEDBACK;
|
||||
|
||||
if (i->getImplType() == m_eLastActiveImpl)
|
||||
return FEEDBACK.value();
|
||||
}
|
||||
|
||||
return firstFeedback.value_or("Ups, no authentication feedack");
|
||||
const std::string& CAuth::getCurrentFailText() {
|
||||
return m_sCurrentFail.failText;
|
||||
}
|
||||
|
||||
std::optional<std::string> CAuth::getFailText(eAuthImplementations implType) {
|
||||
|
|
@ -73,7 +57,11 @@ std::optional<std::string> CAuth::getPrompt(eAuthImplementations implType) {
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
|
||||
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;
|
||||
|
|
@ -88,29 +76,49 @@ void CAuth::terminate() {
|
|||
}
|
||||
}
|
||||
|
||||
static void passwordFailCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->clearPasswordBuffer();
|
||||
g_pAuth->m_iFailedAttempts++;
|
||||
Debug::log(LOG, "Failed attempts: {}", g_pAuth->m_iFailedAttempts);
|
||||
static void unlockCallback(ASP<CTimer> self, void* data) {
|
||||
g_pHyprlock->unlock();
|
||||
}
|
||||
|
||||
void CAuth::enqueueUnlock() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), unlockCallback, nullptr);
|
||||
}
|
||||
|
||||
static void passwordFailCallback(ASP<CTimer> self, void* data) {
|
||||
g_pAuth->m_bDisplayFailText = true;
|
||||
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
|
||||
g_pHyprlock->renderAllOutputs();
|
||||
}
|
||||
|
||||
static void passwordUnlockCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->unlock();
|
||||
static void displayFailTimeoutCallback(ASP<CTimer> self, void* data) {
|
||||
if (g_pAuth->m_bDisplayFailText) {
|
||||
g_pAuth->m_bDisplayFailText = false;
|
||||
g_pHyprlock->renderAllOutputs();
|
||||
}
|
||||
}
|
||||
|
||||
void CAuth::enqueueFail() {
|
||||
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::enqueueUnlock() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordUnlockCallback, nullptr);
|
||||
}
|
||||
|
||||
void CAuth::postActivity(eAuthImplementations implType) {
|
||||
m_eLastActiveImpl = implType;
|
||||
void CAuth::resetDisplayFail() {
|
||||
g_pAuth->m_bDisplayFailText = false;
|
||||
m_resetDisplayFailTimer->cancel();
|
||||
m_resetDisplayFailTimer.reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../core/Timer.hpp"
|
||||
|
||||
enum eAuthImplementations {
|
||||
AUTH_IMPL_PAM = 0,
|
||||
AUTH_IMPL_FINGERPRINT = 1,
|
||||
|
|
@ -28,34 +30,38 @@ class CAuth {
|
|||
public:
|
||||
CAuth();
|
||||
|
||||
void start();
|
||||
void start();
|
||||
|
||||
void submitInput(const std::string& input);
|
||||
bool checkWaiting();
|
||||
void submitInput(const std::string& input);
|
||||
bool checkWaiting();
|
||||
|
||||
// Used by the PasswordInput field. We are constraint to a single line for the authentication feedback there.
|
||||
// Based on m_bDisplayFailText, this will return either the fail text or the prompt.
|
||||
// Based on m_eLastActiveImpl, it will select the implementation.
|
||||
std::string getInlineFeedback();
|
||||
const std::string& getCurrentFailText();
|
||||
|
||||
std::optional<std::string> getFailText(eAuthImplementations implType);
|
||||
std::optional<std::string> getPrompt(eAuthImplementations implType);
|
||||
std::optional<std::string> getFailText(eAuthImplementations implType);
|
||||
std::optional<std::string> getPrompt(eAuthImplementations implType);
|
||||
size_t getFailedAttempts();
|
||||
|
||||
std::shared_ptr<IAuthImplementation> getImpl(eAuthImplementations implType);
|
||||
SP<IAuthImplementation> getImpl(eAuthImplementations implType);
|
||||
|
||||
void terminate();
|
||||
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;
|
||||
size_t m_iFailedAttempts = 0;
|
||||
|
||||
void enqueueUnlock();
|
||||
void enqueueFail();
|
||||
void postActivity(eAuthImplementations implType);
|
||||
bool m_bDisplayFailText = false;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<IAuthImplementation>> m_vImpls;
|
||||
std::optional<eAuthImplementations> m_eLastActiveImpl = std::nullopt;
|
||||
struct {
|
||||
std::string failText = "";
|
||||
eAuthImplementations failSource = AUTH_IMPL_PAM;
|
||||
size_t failedAttempts = 0;
|
||||
} m_sCurrentFail;
|
||||
|
||||
std::vector<SP<IAuthImplementation>> m_vImpls;
|
||||
ASP<CTimer> m_resetDisplayFailTimer;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CAuth> g_pAuth;
|
||||
inline UP<CAuth> g_pAuth;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-m
|
|||
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
|
||||
|
||||
CFingerprint::CFingerprint() {
|
||||
static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:ready_message"));
|
||||
m_sFingerprintReady = *PFINGERPRINTREADY;
|
||||
static auto* const PFINGERPRINTPRESENT = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:present_message"));
|
||||
m_sFingerprintPresent = *PFINGERPRINTPRESENT;
|
||||
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() {
|
||||
|
|
@ -47,8 +47,15 @@ CFingerprint::~CFingerprint() {
|
|||
}
|
||||
|
||||
void CFingerprint::init() {
|
||||
m_sDBUSState.connection = sdbus::createSystemBusConnection();
|
||||
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
|
||||
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());
|
||||
|
|
@ -58,20 +65,13 @@ void CFingerprint::init() {
|
|||
// When entering sleep, the wake signal will trigger startVerify().
|
||||
if (m_sDBUSState.sleeping)
|
||||
return;
|
||||
inhibitSleep();
|
||||
startVerify();
|
||||
});
|
||||
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
|
||||
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
|
||||
if (start) {
|
||||
m_sDBUSState.sleeping = true;
|
||||
stopVerify();
|
||||
m_sDBUSState.inhibitLock.reset();
|
||||
} else {
|
||||
m_sDBUSState.sleeping = false;
|
||||
inhibitSleep();
|
||||
m_sDBUSState.sleeping = start;
|
||||
if (!m_sDBUSState.sleeping && !m_sDBUSState.verifying)
|
||||
startVerify();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -104,18 +104,6 @@ std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
|
|||
return m_sDBUSState.connection;
|
||||
}
|
||||
|
||||
void CFingerprint::inhibitSleep() {
|
||||
m_sDBUSState.login->callMethodAsync("Inhibit")
|
||||
.onInterface(LOGIN_MANAGER)
|
||||
.withArguments("sleep", "hyprlock", "Fingerprint verifcation must be stopped before sleep", "delay")
|
||||
.uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::UnixFd fd) {
|
||||
if (e)
|
||||
Debug::log(WARN, "fprint: could not inhibit sleep: {}", e->what());
|
||||
else
|
||||
m_sDBUSState.inhibitLock = fd;
|
||||
});
|
||||
}
|
||||
|
||||
bool CFingerprint::createDeviceProxy() {
|
||||
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
|
||||
|
||||
|
|
@ -143,8 +131,7 @@ bool CFingerprint::createDeviceProxy() {
|
|||
bool isPresent = presentVariant.get<bool>();
|
||||
if (!isPresent)
|
||||
return;
|
||||
m_sPrompt = m_sFingerprintPresent;
|
||||
m_sFailureReason = "";
|
||||
m_sPrompt = m_sFingerprintPresent;
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
} catch (std::out_of_range& e) {}
|
||||
});
|
||||
|
|
@ -153,66 +140,63 @@ bool CFingerprint::createDeviceProxy() {
|
|||
}
|
||||
|
||||
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
|
||||
g_pAuth->postActivity(AUTH_IMPL_FINGERPRINT);
|
||||
Debug::log(LOG, "fprint: handling status {}", result);
|
||||
auto matchResult = s_mapStringToTestType[result];
|
||||
bool authenticated = false;
|
||||
bool retry = false;
|
||||
if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED)
|
||||
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_sPrompt = "";
|
||||
m_sFailureReason = "Fingerprint auth disabled (too many failed attempts)";
|
||||
} else {
|
||||
done = false;
|
||||
startVerify(true);
|
||||
done = false;
|
||||
static const auto RETRYDELAY = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:retry_delay");
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(*RETRYDELAY), [](ASP<CTimer> self, void* data) { ((CFingerprint*)data)->startVerify(true); }, this);
|
||||
m_sFailureReason = "Fingerprint did not match";
|
||||
}
|
||||
break;
|
||||
case MATCH_UNKNOWN_ERROR:
|
||||
stopVerify();
|
||||
m_sPrompt = "";
|
||||
m_sFailureReason = "Fingerprint auth disabled (unknown error)";
|
||||
break;
|
||||
case MATCH_MATCHED:
|
||||
stopVerify();
|
||||
m_sPrompt = "";
|
||||
m_sFailureReason = "";
|
||||
authenticated = true;
|
||||
authenticated = true;
|
||||
g_pAuth->enqueueUnlock();
|
||||
break;
|
||||
case MATCH_RETRY:
|
||||
retry = true;
|
||||
m_sPrompt = "Please retry fingerprint scan";
|
||||
m_sFailureReason = "";
|
||||
retry = true;
|
||||
m_sPrompt = "Please retry fingerprint scan";
|
||||
break;
|
||||
case MATCH_SWIPE_TOO_SHORT:
|
||||
retry = true;
|
||||
m_sPrompt = "Swipe too short - try again";
|
||||
m_sFailureReason = "";
|
||||
retry = true;
|
||||
m_sPrompt = "Swipe too short - try again";
|
||||
break;
|
||||
case MATCH_FINGER_NOT_CENTERED:
|
||||
retry = true;
|
||||
m_sPrompt = "Finger not centered - try again";
|
||||
m_sFailureReason = "";
|
||||
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";
|
||||
m_sFailureReason = "";
|
||||
retry = true;
|
||||
m_sPrompt = "Remove your finger and try again";
|
||||
break;
|
||||
case MATCH_DISCONNECTED:
|
||||
m_sPrompt = "";
|
||||
m_sFailureReason = "Fingerprint device disconnected";
|
||||
m_sDBUSState.abort = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!authenticated && !retry)
|
||||
g_pAuth->enqueueFail();
|
||||
g_pAuth->enqueueFail(m_sFailureReason, AUTH_IMPL_FINGERPRINT);
|
||||
else if (retry)
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
|
||||
if (done || m_sDBUSState.abort)
|
||||
m_sDBUSState.done = true;
|
||||
|
|
@ -231,6 +215,7 @@ void CFingerprint::claimDevice() {
|
|||
}
|
||||
|
||||
void CFingerprint::startVerify(bool isRetry) {
|
||||
m_sDBUSState.verifying = true;
|
||||
if (!m_sDBUSState.device) {
|
||||
if (!createDeviceProxy())
|
||||
return;
|
||||
|
|
@ -242,26 +227,23 @@ void CFingerprint::startVerify(bool isRetry) {
|
|||
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_sPrompt = "";
|
||||
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.";
|
||||
m_sFailureReason = "";
|
||||
} else {
|
||||
m_sPrompt = m_sFingerprintReady;
|
||||
m_sFailureReason = "";
|
||||
}
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ class CFingerprint : public IAuthImplementation {
|
|||
std::shared_ptr<sdbus::IConnection> connection;
|
||||
std::unique_ptr<sdbus::IProxy> login;
|
||||
std::unique_ptr<sdbus::IProxy> device;
|
||||
sdbus::UnixFd inhibitLock;
|
||||
|
||||
bool abort = false;
|
||||
bool done = false;
|
||||
int retries = 0;
|
||||
bool sleeping = false;
|
||||
bool abort = false;
|
||||
bool done = false;
|
||||
int retries = 0;
|
||||
bool sleeping = false;
|
||||
bool verifying = false;
|
||||
} m_sDBUSState;
|
||||
|
||||
std::string m_sFingerprintReady;
|
||||
|
|
@ -45,8 +45,6 @@ class CFingerprint : public IAuthImplementation {
|
|||
|
||||
void handleVerifyStatus(const std::string& result, const bool done);
|
||||
|
||||
void inhibitSleep();
|
||||
|
||||
bool createDeviceProxy();
|
||||
void claimDevice();
|
||||
void startVerify(bool isRetry = false);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
|
|||
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 = std::move(MSG);
|
||||
CONVERSATIONSTATE->failText = MSG;
|
||||
CONVERSATIONSTATE->failTextFromPam = true;
|
||||
}
|
||||
break;
|
||||
|
|
@ -61,11 +61,11 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
|
|||
}
|
||||
|
||||
CPam::CPam() {
|
||||
static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:pam:module"));
|
||||
m_sPamModule = *PPAMMODULE;
|
||||
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";
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ void CPam::init() {
|
|||
return;
|
||||
|
||||
if (!AUTHENTICATED)
|
||||
g_pAuth->enqueueFail();
|
||||
g_pAuth->enqueueFail(m_sConversationState.failText, AUTH_IMPL_PAM);
|
||||
else {
|
||||
g_pAuth->enqueueUnlock();
|
||||
return;
|
||||
|
|
@ -106,8 +106,8 @@ void CPam::init() {
|
|||
}
|
||||
|
||||
bool CPam::auth() {
|
||||
const pam_conv localConv = {conv, (void*)&m_sConversationState};
|
||||
pam_handle_t* handle = NULL;
|
||||
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)");
|
||||
|
||||
|
|
@ -124,7 +124,6 @@ bool CPam::auth() {
|
|||
handle = nullptr;
|
||||
|
||||
m_sConversationState.waitingForPamAuth = false;
|
||||
g_pAuth->postActivity(AUTH_IMPL_PAM);
|
||||
|
||||
if (ret != PAM_SUCCESS) {
|
||||
if (!m_sConversationState.failTextFromPam)
|
||||
|
|
@ -139,14 +138,7 @@ bool CPam::auth() {
|
|||
return true;
|
||||
}
|
||||
|
||||
// clearing the input must be done from the main thread
|
||||
static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->clearPasswordBuffer();
|
||||
}
|
||||
|
||||
void CPam::waitForInput() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr);
|
||||
|
||||
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
|
||||
m_bBlockInput = false;
|
||||
m_sConversationState.waitingForPamAuth = false;
|
||||
|
|
@ -156,7 +148,6 @@ void CPam::waitForInput() {
|
|||
}
|
||||
|
||||
void CPam::handleInput(const std::string& input) {
|
||||
g_pAuth->postActivity(AUTH_IMPL_PAM);
|
||||
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
|
||||
|
||||
if (!m_sConversationState.inputRequested)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ICustomConfigValueData {
|
|||
|
||||
class CLayoutValueData : public ICustomConfigValueData {
|
||||
public:
|
||||
CLayoutValueData() {};
|
||||
CLayoutValueData() = default;
|
||||
virtual ~CLayoutValueData() {};
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
|
|
@ -62,8 +62,9 @@ class CLayoutValueData : public ICustomConfigValueData {
|
|||
class CGradientValueData : public ICustomConfigValueData {
|
||||
public:
|
||||
CGradientValueData() {};
|
||||
CGradientValueData(CColor col) {
|
||||
CGradientValueData(CHyprColor col) {
|
||||
m_vColors.push_back(col);
|
||||
updateColorsOk();
|
||||
};
|
||||
virtual ~CGradientValueData() {};
|
||||
|
||||
|
|
@ -71,14 +72,29 @@ class CGradientValueData : public ICustomConfigValueData {
|
|||
return CVD_TYPE_GRADIENT;
|
||||
}
|
||||
|
||||
void reset(CColor col) {
|
||||
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<CColor> m_vColors;
|
||||
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;
|
||||
|
|
@ -86,6 +102,7 @@ class CGradientValueData : public ICustomConfigValueData {
|
|||
/* 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;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
#include "ConfigManager.hpp"
|
||||
#include "ConfigDataValues.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../config/ConfigDataValues.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
|
||||
}
|
||||
|
|
@ -25,6 +30,30 @@ static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleBezier(const char* c, const char* v) {
|
||||
const std::string VALUE = v;
|
||||
const std::string COMMAND = c;
|
||||
|
||||
const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);
|
||||
|
||||
Hyprlang::CParseResult result;
|
||||
if (RESULT.has_value())
|
||||
result.setError(RESULT.value().c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {
|
||||
const std::string VALUE = v;
|
||||
const std::string COMMAND = c;
|
||||
|
||||
const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE);
|
||||
|
||||
Hyprlang::CParseResult result;
|
||||
if (RESULT.has_value())
|
||||
result.setError(RESULT.value().c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult configHandleLayoutOption(const char* v, void** data) {
|
||||
const std::string VALUE = v;
|
||||
|
||||
|
|
@ -71,22 +100,33 @@ static void configHandleLayoutOptionDestroy(void** data) {
|
|||
}
|
||||
|
||||
static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) {
|
||||
std::string V = VALUE;
|
||||
const std::string V = VALUE;
|
||||
|
||||
if (!*data)
|
||||
*data = new CGradientValueData();
|
||||
|
||||
const auto DATA = reinterpret_cast<CGradientValueData*>(*data);
|
||||
|
||||
CVarList varlist(V, 0, ' ');
|
||||
DATA->m_vColors.clear();
|
||||
DATA->m_bIsFallback = false;
|
||||
|
||||
std::string parseError = "";
|
||||
std::string rolling = V;
|
||||
|
||||
for (auto const& var : varlist) {
|
||||
if (var.find("deg") != std::string::npos) {
|
||||
// last arg
|
||||
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 (...) {
|
||||
|
|
@ -95,7 +135,8 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void**
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
@ -107,7 +148,7 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void**
|
|||
continue;
|
||||
|
||||
try {
|
||||
DATA->m_vColors.push_back(CColor(configStringToInt(var)));
|
||||
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();
|
||||
|
|
@ -116,16 +157,18 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void**
|
|||
|
||||
if (V.empty()) {
|
||||
DATA->m_bIsFallback = true;
|
||||
DATA->m_vColors.push_back(0); // transparent
|
||||
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.push_back(0); // transparent
|
||||
DATA->m_vColors.emplace_back(0); // transparent
|
||||
}
|
||||
|
||||
DATA->updateColorsOk();
|
||||
|
||||
Hyprlang::CParseResult result;
|
||||
if (!parseError.empty())
|
||||
result.setError(parseError.c_str());
|
||||
|
|
@ -166,21 +209,25 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
|
||||
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
|
||||
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
|
||||
m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0});
|
||||
|
||||
#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: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{""});
|
||||
|
|
@ -212,6 +259,7 @@ void CConfigManager::init() {
|
|||
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{""});
|
||||
|
|
@ -228,6 +276,7 @@ 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{""});
|
||||
|
|
@ -239,7 +288,6 @@ void CConfigManager::init() {
|
|||
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_fade_time", Hyprlang::INT{200});
|
||||
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});
|
||||
|
|
@ -250,12 +298,11 @@ void CConfigManager::init() {
|
|||
m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0"));
|
||||
m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"});
|
||||
m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0});
|
||||
m_config.addSpecialConfigValue("input-field", "hide_input_base_color", Hyprlang::INT{0xEE00FF99});
|
||||
m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1});
|
||||
m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88"));
|
||||
m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222"));
|
||||
m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_timeout", Hyprlang::INT{2000});
|
||||
m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300});
|
||||
m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG(""));
|
||||
m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG(""));
|
||||
m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG(""));
|
||||
|
|
@ -271,14 +318,40 @@ void CConfigManager::init() {
|
|||
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();
|
||||
|
||||
|
|
@ -288,13 +361,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() {
|
||||
|
|
@ -306,14 +373,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())},
|
||||
|
|
@ -337,9 +407,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())},
|
||||
|
|
@ -352,6 +422,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
|
||||
|
|
@ -362,9 +433,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())},
|
||||
|
|
@ -378,6 +449,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
|
||||
|
|
@ -387,9 +459,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())},
|
||||
|
|
@ -398,7 +470,6 @@ 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_fade_time", m_config.getSpecialConfigValue("input-field", "dots_fade_time", 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())},
|
||||
|
|
@ -409,12 +480,11 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
|
|||
{"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_timeout", m_config.getSpecialConfigValue("input-field", "fail_timeout", k.c_str())},
|
||||
{"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", k.c_str())},
|
||||
{"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())},
|
||||
{"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())},
|
||||
{"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())},
|
||||
|
|
@ -431,9 +501,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())},
|
||||
|
|
@ -445,6 +515,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
|
||||
|
|
@ -498,3 +569,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,16 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#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);
|
||||
void init();
|
||||
|
||||
template <typename T>
|
||||
Hyprlang::CSimpleConfigValue<T> getValue(const std::string& name) {
|
||||
return Hyprlang::CSimpleConfigValue<T>(&m_config, name.c_str());
|
||||
}
|
||||
|
||||
struct SWidgetConfig {
|
||||
std::string type;
|
||||
|
|
@ -19,13 +26,18 @@ class CConfigManager {
|
|||
std::unordered_map<std::string, std::any> values;
|
||||
};
|
||||
|
||||
std::vector<SWidgetConfig> getWidgetConfigs();
|
||||
std::optional<std::string> handleSource(const std::string&, const std::string&);
|
||||
std::vector<SWidgetConfig> getWidgetConfigs();
|
||||
|
||||
std::string configCurrentPath;
|
||||
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;
|
||||
|
|
|
|||
126
src/core/AnimationManager.cpp
Normal file
126
src/core/AnimationManager.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#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 (const auto& PAV : m_vActiveAnimatedVariables) {
|
||||
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;
|
||||
}
|
||||
31
src/core/AnimationManager.hpp
Normal file
31
src/core/AnimationManager.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#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>;
|
||||
pav = makeUnique<CAnimatedVariable<VarType>>();
|
||||
|
||||
pav->create2(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), pav, v);
|
||||
pav->setConfig(pConfig);
|
||||
}
|
||||
|
||||
bool m_bTickScheduled = false;
|
||||
};
|
||||
|
||||
inline UP<CHyprlockAnimationManager> g_pAnimationManager;
|
||||
|
|
@ -9,12 +9,16 @@ CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
|
|||
}
|
||||
|
||||
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
|
||||
if (!dev)
|
||||
if (!g_pSeatManager->m_pPointer)
|
||||
return;
|
||||
|
||||
if (!dev)
|
||||
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
|
||||
|
||||
shapeChanged = true;
|
||||
dev->sendSetShape(lastCursorSerial, shape);
|
||||
}
|
||||
|
||||
void CCursorShape::hideCursor() {
|
||||
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ class CCursorShape {
|
|||
void hideCursor();
|
||||
|
||||
uint32_t lastCursorSerial = 0;
|
||||
bool shapeChanged = false;
|
||||
|
||||
private:
|
||||
SP<CCWpCursorShapeManagerV1> mgr = nullptr;
|
||||
SP<CCWpCursorShapeDeviceV1> dev = nullptr;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,21 +31,22 @@ 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);
|
||||
EGLint matched = 0;
|
||||
const char* vendorString = nullptr;
|
||||
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr);
|
||||
EGLint matched = 0;
|
||||
if (eglDisplay == EGL_NO_DISPLAY) {
|
||||
Debug::log(CRIT, "Failed to create EGL display");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE) {
|
||||
if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) {
|
||||
Debug::log(CRIT, "Failed to initialize EGL");
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -65,6 +66,9 @@ CEGL::CEGL(wl_display* display) {
|
|||
goto error;
|
||||
}
|
||||
|
||||
vendorString = eglQueryString(eglDisplay, EGL_VENDOR);
|
||||
m_isNvidia = (vendorString) ? std::string{vendorString}.contains("NVIDIA") : false;
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
|
|
@ -83,4 +87,4 @@ CEGL::~CEGL() {
|
|||
|
||||
void CEGL::makeCurrent(EGLSurface surf) {
|
||||
eglMakeCurrent(eglDisplay, surf, surf, eglContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
class CEGL {
|
||||
public:
|
||||
CEGL(wl_display*);
|
||||
|
|
@ -17,6 +17,8 @@ class CEGL {
|
|||
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT;
|
||||
|
||||
void makeCurrent(EGLSurface surf);
|
||||
|
||||
bool m_isNvidia = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CEGL> g_pEGL;
|
||||
inline UP<CEGL> g_pEGL;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
#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"
|
||||
#include "src/config/ConfigManager.hpp"
|
||||
|
||||
CSessionLockSurface::~CSessionLockSurface() {
|
||||
if (frameCallback)
|
||||
frameCallback.reset();
|
||||
|
||||
if (eglSurface)
|
||||
eglDestroySurface(g_pEGL->eglDisplay, eglSurface);
|
||||
|
||||
if (eglWindow)
|
||||
wl_egl_window_destroy(eglWindow);
|
||||
}
|
||||
|
||||
CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
|
||||
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);
|
||||
}
|
||||
|
||||
const auto PFRACTIONALSCALING = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:fractional_scaling");
|
||||
const auto ENABLE_FSV1 = **PFRACTIONALSCALING == 1 || /* auto enable */ (**PFRACTIONALSCALING == 2);
|
||||
const auto PFRACTIONALMGR = g_pHyprlock->getFractionalMgr();
|
||||
const auto PVIEWPORTER = g_pHyprlock->getViewporter();
|
||||
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()));
|
||||
|
|
@ -44,12 +47,8 @@ CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
|
|||
if (!PVIEWPORTER)
|
||||
Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!");
|
||||
|
||||
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), output->output->resource()));
|
||||
|
||||
if (!lockSurface) {
|
||||
Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1");
|
||||
exit(1);
|
||||
}
|
||||
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource()));
|
||||
RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1");
|
||||
|
||||
lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) { configure({(double)width, (double)height}, serial); });
|
||||
}
|
||||
|
|
@ -61,6 +60,8 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
|
|||
const bool SAMESIZE = logicalSize == size_;
|
||||
const bool SAMESCALE = appliedScale == fractionalScale;
|
||||
|
||||
const auto POUTPUT = m_outputRef.lock();
|
||||
|
||||
serial = serial_;
|
||||
logicalSize = size_;
|
||||
appliedScale = fractionalScale;
|
||||
|
|
@ -70,8 +71,8 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
|
|||
viewport->sendSetDestination(logicalSize.x, logicalSize.y);
|
||||
surface->sendSetBufferScale(1);
|
||||
} else {
|
||||
size = size_ * output->scale;
|
||||
surface->sendSetBufferScale(output->scale);
|
||||
size = size_ * POUTPUT->scale;
|
||||
surface->sendSetBufferScale(POUTPUT->scale);
|
||||
}
|
||||
|
||||
if (!SAMESERIAL)
|
||||
|
|
@ -83,27 +84,18 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
|
|||
|
||||
if (!eglWindow) {
|
||||
eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y);
|
||||
if (!eglWindow) {
|
||||
Debug::log(CRIT, "Couldn't create eglWindow");
|
||||
exit(1);
|
||||
}
|
||||
RASSERT(eglWindow, "Couldn't create eglWindow");
|
||||
} else
|
||||
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
|
||||
|
||||
if (!eglSurface) {
|
||||
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
|
||||
if (!eglSurface) {
|
||||
Debug::log(CRIT, "Couldn't create eglSurface: {}", (int)eglGetError());
|
||||
// Clean up resources to prevent leaks
|
||||
wl_egl_window_destroy(eglWindow);
|
||||
eglWindow = nullptr;
|
||||
exit(1); // Consider graceful exit or fallback
|
||||
}
|
||||
RASSERT(eglSurface, "Couldn't create eglSurface");
|
||||
}
|
||||
|
||||
if (readyForFrame && !(SAMESIZE && SAMESCALE)) {
|
||||
g_pRenderer->removeWidgetsFor(this);
|
||||
Debug::log(LOG, "Reloading widgets");
|
||||
Debug::log(LOG, "output {} changed, reloading widgets!", POUTPUT->stringPort);
|
||||
g_pRenderer->reconfigureWidgetsFor(POUTPUT->m_ID);
|
||||
}
|
||||
|
||||
readyForFrame = true;
|
||||
|
|
@ -116,25 +108,33 @@ void CSessionLockSurface::onScaleUpdate() {
|
|||
}
|
||||
|
||||
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 = makeShared<CCWlCallback>(surface->sendFrame());
|
||||
frameCallback->setDone([this](CCWlCallback* r, uint32_t data) {
|
||||
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() {
|
||||
|
|
@ -144,4 +144,8 @@ void CSessionLockSurface::onCallback() {
|
|||
needsFrame = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
|
||||
return surface;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,21 +14,24 @@ class CRenderer;
|
|||
|
||||
class CSessionLockSurface {
|
||||
public:
|
||||
CSessionLockSurface(COutput* output);
|
||||
CSessionLockSurface(const SP<COutput>& pOutput);
|
||||
~CSessionLockSurface();
|
||||
|
||||
void configure(const Vector2D& size, uint32_t serial);
|
||||
void configure(const Vector2D& size, uint32_t serial);
|
||||
|
||||
bool readyForFrame = false;
|
||||
bool readyForFrame = false;
|
||||
|
||||
float fractionalScale = 1.0;
|
||||
float fractionalScale = 1.0;
|
||||
|
||||
void render();
|
||||
void onCallback();
|
||||
void onScaleUpdate();
|
||||
void render();
|
||||
void onCallback();
|
||||
void onScaleUpdate();
|
||||
SP<CCWlSurface> getWlSurface();
|
||||
|
||||
private:
|
||||
COutput* output = nullptr;
|
||||
WP<COutput> m_outputRef;
|
||||
OUTPUTID m_outputID = OUTPUT_INVALID;
|
||||
|
||||
SP<CCWlSurface> surface = nullptr;
|
||||
SP<CCExtSessionLockSurfaceV1> lockSurface = nullptr;
|
||||
uint32_t serial = 0;
|
||||
|
|
@ -42,8 +45,12 @@ class CSessionLockSurface {
|
|||
|
||||
bool needsFrame = false;
|
||||
|
||||
uint32_t m_lastFrameTime = 0;
|
||||
uint32_t m_frames = 0;
|
||||
|
||||
// wayland callbacks
|
||||
SP<CCWlCallback> frameCallback = nullptr;
|
||||
|
||||
friend class CRenderer;
|
||||
};
|
||||
friend class COutput;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,32 +1,35 @@
|
|||
#include "Output.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "hyprlock.hpp"
|
||||
#include "../renderer/Renderer.hpp"
|
||||
|
||||
COutput::COutput(SP<CCWlOutput> output_, uint32_t name_) : name(name_), output(output_) {
|
||||
output->setDescription([this](CCWlOutput* r, const char* description) {
|
||||
void COutput::create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t _name) {
|
||||
m_ID = _name;
|
||||
m_wlOutput = pWlOutput;
|
||||
m_self = pSelf;
|
||||
|
||||
m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) {
|
||||
stringDesc = description ? std::string{description} : "";
|
||||
Debug::log(LOG, "output {} description {}", name, stringDesc);
|
||||
Debug::log(LOG, "output {} description {}", m_ID, stringDesc);
|
||||
});
|
||||
|
||||
output->setName([this](CCWlOutput* r, const char* name) {
|
||||
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);
|
||||
});
|
||||
|
||||
output->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
|
||||
m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
|
||||
|
||||
output->setDone([this](CCWlOutput* r) {
|
||||
Debug::log(LOG, "output {} done", name);
|
||||
if (g_pHyprlock->m_bLocked && !sessionLockSurface) {
|
||||
// if we are already locked, create a surface dynamically
|
||||
Debug::log(LOG, "Creating a surface dynamically for output as we are already locked");
|
||||
sessionLockSurface = std::make_unique<CSessionLockSurface>(this);
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||
m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||
// handle portrait mode and flipped cases
|
||||
if (transform % 2 == 1)
|
||||
size = {height, width};
|
||||
|
|
@ -34,10 +37,33 @@ COutput::COutput(SP<CCWlOutput> output_, uint32_t name_) : name(name_), output(o
|
|||
size = {width, height};
|
||||
});
|
||||
|
||||
output->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;
|
||||
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 {}", name, make ? make : "", model ? model : "");
|
||||
Debug::log(LOG, "output {} make {} model {}", m_ID, make ? make : "", model ? model : "");
|
||||
});
|
||||
}
|
||||
|
||||
void COutput::createSessionLockSurface() {
|
||||
if (!m_self.valid()) {
|
||||
Debug::log(ERR, "output {} dead??", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sessionLockSurface) {
|
||||
Debug::log(ERR, "output {} already has a session lock surface", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size == Vector2D{0, 0}) {
|
||||
Debug::log(WARN, "output {} refusing to create a lock surface with size 0x0", m_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sessionLockSurface = makeUnique<CSessionLockSurface>(m_self.lock());
|
||||
}
|
||||
|
||||
Vector2D COutput::getViewport() const {
|
||||
return (m_sessionLockSurface) ? m_sessionLockSurface->size : size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,32 @@
|
|||
|
||||
#include "../defines.hpp"
|
||||
#include "wayland.hpp"
|
||||
#include "../helpers/Math.hpp"
|
||||
#include "LockSurface.hpp"
|
||||
#include <memory>
|
||||
|
||||
class COutput {
|
||||
public:
|
||||
COutput(SP<CCWlOutput> output, uint32_t name);
|
||||
COutput() = default;
|
||||
~COutput() = default;
|
||||
|
||||
uint32_t name = 0;
|
||||
bool focused = false;
|
||||
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
Vector2D size;
|
||||
int scale = 1;
|
||||
std::string stringName = "";
|
||||
std::string stringPort = "";
|
||||
std::string stringDesc = "";
|
||||
void create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t name);
|
||||
|
||||
std::unique_ptr<CSessionLockSurface> sessionLockSurface;
|
||||
OUTPUTID m_ID = 0;
|
||||
bool focused = false;
|
||||
bool done = false;
|
||||
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
Vector2D size;
|
||||
int scale = 1;
|
||||
std::string stringName = "";
|
||||
std::string stringPort = "";
|
||||
std::string stringDesc = "";
|
||||
|
||||
SP<CCWlOutput> output = nullptr;
|
||||
UP<CSessionLockSurface> m_sessionLockSurface;
|
||||
|
||||
private:
|
||||
SP<CCWlOutput> m_wlOutput = nullptr;
|
||||
|
||||
WP<COutput> m_self;
|
||||
|
||||
void createSessionLockSurface();
|
||||
|
||||
Vector2D getViewport() const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@
|
|||
#include "../config/ConfigManager.hpp"
|
||||
#include <chrono>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
|
||||
CSeatManager::~CSeatManager() {
|
||||
if (m_pCursorShape && m_pCursorShape->shapeChanged)
|
||||
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
|
||||
|
||||
if (m_pXKBState)
|
||||
xkb_state_unref(m_pXKBState);
|
||||
if (m_pXKBKeymap)
|
||||
|
|
@ -25,7 +30,13 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
|
|||
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;
|
||||
|
||||
|
|
@ -39,19 +50,43 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
|
|||
if (!m_pCursorShape)
|
||||
return;
|
||||
|
||||
static auto* const PHIDE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:hide_cursor");
|
||||
|
||||
m_pCursorShape->lastCursorSerial = serial;
|
||||
|
||||
if (**PHIDE)
|
||||
if (*HIDECURSOR)
|
||||
m_pCursorShape->hideCursor();
|
||||
else
|
||||
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
|
||||
|
||||
g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
|
||||
|
||||
if (*HIDECURSOR)
|
||||
return;
|
||||
|
||||
for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
|
||||
if (!POUTPUT->m_sessionLockSurface)
|
||||
continue;
|
||||
|
||||
const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
|
||||
if (PWLSURFACE->resource() == surf)
|
||||
g_pHyprlock->m_focusedOutput = POUTPUT;
|
||||
}
|
||||
});
|
||||
|
||||
m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });
|
||||
|
||||
m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
|
||||
if (*HIDECURSOR)
|
||||
return;
|
||||
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
|
||||
});
|
||||
}
|
||||
|
||||
if (caps & WL_SEAT_CAPABILITY_TOUCH) {
|
||||
m_pTouch = makeShared<CCWlTouch>(r->sendGetTouch());
|
||||
m_pTouch->setDown([](CCWlTouch* r, uint32_t serial, uint32_t time, wl_proxy* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) {
|
||||
g_pHyprlock->onClick(BTN_LEFT, true, {wl_fixed_to_double(x), wl_fixed_to_double(y)});
|
||||
});
|
||||
m_pTouch->setUp([](CCWlTouch* r, uint32_t serial, uint32_t time, int32_t id) { g_pHyprlock->onClick(BTN_LEFT, false, {0, 0}); });
|
||||
};
|
||||
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
||||
m_pKeeb = makeShared<CCWlKeyboard>(r->sendGetKeyboard());
|
||||
|
||||
|
|
@ -130,7 +165,7 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
|
|||
}
|
||||
|
||||
void CSeatManager::registerCursorShape(SP<CCWpCursorShapeManagerV1> shape) {
|
||||
m_pCursorShape = std::make_unique<CCursorShape>(shape);
|
||||
m_pCursorShape = makeUnique<CCursorShape>(shape);
|
||||
}
|
||||
|
||||
bool CSeatManager::registered() {
|
||||
|
|
|
|||
|
|
@ -5,29 +5,29 @@
|
|||
#include "wayland.hpp"
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
#include <memory>
|
||||
|
||||
class CSeatManager {
|
||||
public:
|
||||
CSeatManager() = default;
|
||||
~CSeatManager();
|
||||
|
||||
void registerSeat(SP<CCWlSeat> seat);
|
||||
void registerCursorShape(SP<CCWpCursorShapeManagerV1> shape);
|
||||
bool registered();
|
||||
void registerSeat(SP<CCWlSeat> seat);
|
||||
void registerCursorShape(SP<CCWpCursorShapeManagerV1> shape);
|
||||
bool registered();
|
||||
|
||||
SP<CCWlKeyboard> m_pKeeb;
|
||||
SP<CCWlPointer> m_pPointer;
|
||||
SP<CCWlKeyboard> m_pKeeb;
|
||||
SP<CCWlPointer> m_pPointer;
|
||||
SP<CCWlTouch> m_pTouch;
|
||||
|
||||
std::unique_ptr<CCursorShape> m_pCursorShape;
|
||||
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;
|
||||
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 std::unique_ptr<CSeatManager> g_pSeatManager = std::make_unique<CSeatManager>();
|
||||
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_) {
|
||||
expires = std::chrono::system_clock::now() + timeout;
|
||||
allowForceUpdate = force;
|
||||
CTimer::CTimer(std::chrono::system_clock::duration timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data_, bool force) :
|
||||
cb(cb_), data(data_), allowForceUpdate(force) {
|
||||
expires = std::chrono::system_clock::now() + timeout;
|
||||
}
|
||||
|
||||
bool CTimer::passed() {
|
||||
|
|
@ -17,7 +17,7 @@ bool CTimer::cancelled() {
|
|||
return wasCancelled;
|
||||
}
|
||||
|
||||
void CTimer::call(std::shared_ptr<CTimer> self) {
|
||||
void CTimer::call(ASP<CTimer> self) {
|
||||
cb(self, data);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include "../defines.hpp"
|
||||
|
||||
class CTimer {
|
||||
public:
|
||||
CTimer(std::chrono::system_clock::duration timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force);
|
||||
CTimer(std::chrono::system_clock::duration timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data_, bool force);
|
||||
|
||||
void cancel();
|
||||
bool passed();
|
||||
|
|
@ -14,12 +15,12 @@ class CTimer {
|
|||
float leftMs();
|
||||
|
||||
bool cancelled();
|
||||
void call(std::shared_ptr<CTimer> self);
|
||||
void call(ASP<CTimer> self);
|
||||
|
||||
private:
|
||||
std::function<void(std::shared_ptr<CTimer> self, void* data)> cb;
|
||||
void* data = nullptr;
|
||||
std::chrono::system_clock::time_point expires;
|
||||
bool wasCancelled = false;
|
||||
bool allowForceUpdate = false;
|
||||
std::function<void(ASP<CTimer> self, void* data)> cb;
|
||||
void* data = nullptr;
|
||||
std::chrono::system_clock::time_point expires;
|
||||
bool wasCancelled = false;
|
||||
bool allowForceUpdate = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,46 +2,54 @@
|
|||
#include "../helpers/Log.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../renderer/Renderer.hpp"
|
||||
#include "../renderer/AsyncResourceManager.hpp"
|
||||
#include "../auth/Auth.hpp"
|
||||
#include "../auth/Fingerprint.hpp"
|
||||
#include "Egl.hpp"
|
||||
#include "./Egl.hpp"
|
||||
#include "./Seat.hpp"
|
||||
#include <chrono>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <csignal>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <xf86drm.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <malloc.h>
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool noFadeIn) {
|
||||
static void setMallocThreshold() {
|
||||
#ifdef M_TRIM_THRESHOLD
|
||||
// The default is 128 pages,
|
||||
// which is very large and can lead to a lot of memory used for no reason
|
||||
// because trimming hasn't happened
|
||||
static const int PAGESIZE = sysconf(_SC_PAGESIZE);
|
||||
mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediateRender, const int graceSeconds) {
|
||||
setMallocThreshold();
|
||||
|
||||
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
|
||||
if (!m_sWaylandState.display) {
|
||||
Debug::log(CRIT, "Couldn't connect to a wayland compositor");
|
||||
exit(1);
|
||||
}
|
||||
RASSERT(m_sWaylandState.display, "Couldn't connect to a wayland compositor");
|
||||
|
||||
g_pEGL = std::make_unique<CEGL>(m_sWaylandState.display);
|
||||
g_pEGL = makeUnique<CEGL>(m_sWaylandState.display);
|
||||
|
||||
if (!immediate) {
|
||||
const auto PGRACE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:grace");
|
||||
m_tGraceEnds = **PGRACE ? std::chrono::system_clock::now() + std::chrono::seconds(**PGRACE) : std::chrono::system_clock::from_time_t(0);
|
||||
} else
|
||||
if (graceSeconds > 0)
|
||||
m_tGraceEnds = std::chrono::system_clock::now() + std::chrono::seconds(graceSeconds);
|
||||
else
|
||||
m_tGraceEnds = std::chrono::system_clock::from_time_t(0);
|
||||
|
||||
const auto PIMMEDIATERENDER = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:immediate_render");
|
||||
m_bImmediateRender = immediateRender || **PIMMEDIATERENDER;
|
||||
|
||||
const auto* const PNOFADEIN = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_in");
|
||||
m_bNoFadeIn = noFadeIn || **PNOFADEIN;
|
||||
static const auto IMMEDIATERENDER = g_pConfigManager->getValue<Hyprlang::INT>("general:immediate_render");
|
||||
m_bImmediateRender = immediateRender || *IMMEDIATERENDER;
|
||||
|
||||
const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP");
|
||||
const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""};
|
||||
|
|
@ -64,7 +72,7 @@ static void registerSignalAction(int sig, void (*handler)(int), int sa_flags = 0
|
|||
static void handleUnlockSignal(int sig) {
|
||||
if (sig == SIGUSR1) {
|
||||
Debug::log(LOG, "Unlocking with a SIGUSR1");
|
||||
g_pHyprlock->releaseSessionLock();
|
||||
g_pAuth->enqueueUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,20 +91,6 @@ static void handlePollTerminate(int sig) {
|
|||
;
|
||||
}
|
||||
|
||||
static void handleCriticalSignal(int sig) {
|
||||
g_pHyprlock->attemptRestoreOnDeath();
|
||||
|
||||
// remove our handlers
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGABRT, &sa, nullptr);
|
||||
sigaction(SIGSEGV, &sa, nullptr);
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
static char* gbm_find_render_node(drmDevice* device) {
|
||||
drmDevice* devices[64];
|
||||
char* render_node = nullptr;
|
||||
|
|
@ -158,7 +152,7 @@ void CHyprlock::addDmabufListener() {
|
|||
uint64_t modifier;
|
||||
};
|
||||
// An entry in the table has to be 16 bytes long
|
||||
assert(sizeof(fm_entry) == 16);
|
||||
static_assert(sizeof(fm_entry) == 16);
|
||||
|
||||
uint32_t n_modifiers = dma.formatTableSize / sizeof(fm_entry);
|
||||
fm_entry* fm_entry = (struct fm_entry*)dma.formatTable;
|
||||
|
|
@ -232,10 +226,7 @@ void CHyprlock::addDmabufListener() {
|
|||
memcpy(&device, device_arr->data, sizeof(device));
|
||||
|
||||
drmDevice* drmDev;
|
||||
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
|
||||
Debug::log(WARN, "[dmabuf] unable to open main device?");
|
||||
exit(1);
|
||||
}
|
||||
RASSERT(drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) == 0, "unable to open main device?");
|
||||
|
||||
dma.gbmDevice = createGBMDevice(drmDev);
|
||||
drmFreeDevice(&drmDev);
|
||||
|
|
@ -246,6 +237,18 @@ void CHyprlock::addDmabufListener() {
|
|||
});
|
||||
}
|
||||
|
||||
void CHyprlock::removeDmabufListener() {
|
||||
if (dma.linuxDmabufFeedback) {
|
||||
dma.linuxDmabufFeedback->sendDestroy();
|
||||
dma.linuxDmabufFeedback.reset();
|
||||
}
|
||||
|
||||
if (dma.linuxDmabuf) {
|
||||
dma.linuxDmabuf->sendDestroy();
|
||||
dma.linuxDmabuf.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprlock::run() {
|
||||
m_sWaylandState.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_sWaylandState.display));
|
||||
m_sWaylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) {
|
||||
|
|
@ -268,14 +271,15 @@ void CHyprlock::run() {
|
|||
return;
|
||||
}
|
||||
|
||||
g_pSeatManager->registerSeat(makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_seat_interface, 9)));
|
||||
g_pSeatManager->registerSeat(makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_seat_interface, 8)));
|
||||
} else if (IFACE == ext_session_lock_manager_v1_interface.name)
|
||||
m_sWaylandState.sessionLock =
|
||||
makeShared<CCExtSessionLockManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &ext_session_lock_manager_v1_interface, 1));
|
||||
else if (IFACE == wl_output_interface.name)
|
||||
m_vOutputs.emplace_back(
|
||||
std::make_unique<COutput>(makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name));
|
||||
else if (IFACE == wp_cursor_shape_manager_v1_interface.name)
|
||||
else if (IFACE == wl_output_interface.name) {
|
||||
const auto POUTPUT = makeShared<COutput>();
|
||||
POUTPUT->create(POUTPUT, makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name);
|
||||
m_vOutputs.emplace_back(POUTPUT);
|
||||
} else if (IFACE == wp_cursor_shape_manager_v1_interface.name)
|
||||
g_pSeatManager->registerCursorShape(
|
||||
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_cursor_shape_manager_v1_interface, 1)));
|
||||
else if (IFACE == wl_compositor_interface.name)
|
||||
|
|
@ -288,6 +292,8 @@ void CHyprlock::run() {
|
|||
else if (IFACE == zwlr_screencopy_manager_v1_interface.name)
|
||||
m_sWaylandState.screencopy =
|
||||
makeShared<CCZwlrScreencopyManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &zwlr_screencopy_manager_v1_interface, 3));
|
||||
else if (IFACE == wl_shm_interface.name)
|
||||
m_sWaylandState.shm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_shm_interface, 1));
|
||||
else
|
||||
return;
|
||||
|
||||
|
|
@ -295,9 +301,9 @@ void CHyprlock::run() {
|
|||
});
|
||||
m_sWaylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) {
|
||||
Debug::log(LOG, " | removed iface {}", name);
|
||||
auto outputIt = std::find_if(m_vOutputs.begin(), m_vOutputs.end(), [name](const auto& other) { return other->name == name; });
|
||||
auto outputIt = std::ranges::find_if(m_vOutputs, [id = name](const auto& other) { return other->m_ID == id; });
|
||||
if (outputIt != m_vOutputs.end()) {
|
||||
g_pRenderer->removeWidgetsFor(outputIt->get()->sessionLockSurface.get());
|
||||
g_pRenderer->removeWidgetsFor((*outputIt)->m_ID);
|
||||
m_vOutputs.erase(outputIt);
|
||||
}
|
||||
});
|
||||
|
|
@ -312,37 +318,26 @@ void CHyprlock::run() {
|
|||
// gather info about monitors
|
||||
wl_display_roundtrip(m_sWaylandState.display);
|
||||
|
||||
g_pRenderer = std::make_unique<CRenderer>();
|
||||
g_pAuth = std::make_unique<CAuth>();
|
||||
g_pRenderer = makeUnique<CRenderer>();
|
||||
g_asyncResourceManager = makeUnique<CAsyncResourceManager>();
|
||||
g_pAuth = makeUnique<CAuth>();
|
||||
g_pAuth->start();
|
||||
|
||||
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
|
||||
const bool NOFADEOUT = **PNOFADEOUT;
|
||||
|
||||
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
|
||||
|
||||
// Hyprland violates the protocol a bit to allow for this.
|
||||
if (m_sCurrentDesktop != "Hyprland") {
|
||||
while (!g_pRenderer->asyncResourceGatherer->gathered) {
|
||||
wl_display_flush(m_sWaylandState.display);
|
||||
if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
|
||||
wl_display_read_events(m_sWaylandState.display);
|
||||
wl_display_dispatch_pending(m_sWaylandState.display);
|
||||
} else {
|
||||
wl_display_dispatch(m_sWaylandState.display);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
g_asyncResourceManager->enqueueStaticAssets();
|
||||
g_asyncResourceManager->enqueueScreencopyFrames();
|
||||
|
||||
acquireSessionLock();
|
||||
if (!g_pHyprlock->m_bImmediateRender)
|
||||
// Gather background resources and screencopy frames before locking the screen.
|
||||
// We need to do this because as soon as we lock the screen, workspaces frames can no longer be captured. It either won't work at all, or we will capture hyprlock itself.
|
||||
// Bypass with --immediate-render (can cause the background first rendering a solid color and missing or inaccurate screencopy frames)
|
||||
g_asyncResourceManager->gatherInitialResources(m_sWaylandState.display);
|
||||
|
||||
// Recieved finished
|
||||
if (m_bTerminate) {
|
||||
// Failed to lock the session
|
||||
if (!acquireSessionLock()) {
|
||||
m_sLoopState.timerEvent = true;
|
||||
m_sLoopState.timerCV.notify_all();
|
||||
g_pRenderer->asyncResourceGatherer->notify();
|
||||
g_pRenderer->asyncResourceGatherer->await();
|
||||
g_pAuth->terminate();
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -353,10 +348,6 @@ void CHyprlock::run() {
|
|||
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
|
||||
registerSignalAction(SIGUSR2, handleForceUpdateSignal);
|
||||
registerSignalAction(SIGRTMIN, handlePollTerminate);
|
||||
registerSignalAction(SIGSEGV, handleCriticalSignal);
|
||||
registerSignalAction(SIGABRT, handleCriticalSignal);
|
||||
|
||||
createSessionLockSurfaces();
|
||||
|
||||
pollfd pollfds[2];
|
||||
pollfds[0] = {
|
||||
|
|
@ -373,32 +364,33 @@ void CHyprlock::run() {
|
|||
|
||||
std::thread pollThr([this, &pollfds, fdcount]() {
|
||||
while (!m_bTerminate) {
|
||||
int ret = poll(pollfds, fdcount, 5000 /* 5 seconds, reasonable. Just in case we need to terminate and the signal fails */);
|
||||
bool preparedToRead = wl_display_prepare_read(m_sWaylandState.display) == 0;
|
||||
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
int events = 0;
|
||||
if (preparedToRead) {
|
||||
events = poll(pollfds, fdcount, 5000);
|
||||
|
||||
if (events < 0) {
|
||||
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
|
||||
wl_display_cancel_read(m_sWaylandState.display);
|
||||
continue;
|
||||
|
||||
Debug::log(CRIT, "[core] Polling fds failed with {}", errno);
|
||||
attemptRestoreOnDeath();
|
||||
m_bTerminate = true;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fdcount; ++i) {
|
||||
if (pollfds[i].revents & POLLHUP) {
|
||||
Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i);
|
||||
attemptRestoreOnDeath();
|
||||
m_bTerminate = true;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fdcount; ++i) {
|
||||
RASSERT(!(pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i);
|
||||
}
|
||||
|
||||
wl_display_read_events(m_sWaylandState.display);
|
||||
m_sLoopState.wlDispatched = false;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
if (events > 0 || !preparedToRead) {
|
||||
Debug::log(TRACE, "[core] got poll event");
|
||||
std::lock_guard<std::mutex> lg2(m_sLoopState.eventLoopMutex);
|
||||
std::unique_lock lk(m_sLoopState.eventLoopMutex);
|
||||
m_sLoopState.event = true;
|
||||
m_sLoopState.loopCV.notify_all();
|
||||
|
||||
m_sLoopState.wlDispatchCV.wait_for(lk, std::chrono::milliseconds(100), [this] { return m_sLoopState.wlDispatched; });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -411,8 +403,7 @@ void CHyprlock::run() {
|
|||
float least = 10000;
|
||||
for (auto& t : m_vTimers) {
|
||||
const auto TIME = std::clamp(t->leftMs(), 1.f, INFINITY);
|
||||
if (TIME < least)
|
||||
least = TIME;
|
||||
least = std::min(TIME, least);
|
||||
}
|
||||
|
||||
m_sLoopState.timersMutex.unlock();
|
||||
|
|
@ -430,17 +421,13 @@ void CHyprlock::run() {
|
|||
});
|
||||
|
||||
m_sLoopState.event = true; // let it process once
|
||||
g_pRenderer->startFadeIn();
|
||||
|
||||
while (!m_bTerminate) {
|
||||
std::unique_lock lk(m_sLoopState.eventRequestMutex);
|
||||
if (m_sLoopState.event == false)
|
||||
if (!m_sLoopState.event)
|
||||
m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; });
|
||||
|
||||
if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) {
|
||||
releaseSessionLock();
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_bTerminate)
|
||||
break;
|
||||
|
||||
|
|
@ -448,72 +435,33 @@ void CHyprlock::run() {
|
|||
|
||||
m_sLoopState.event = false;
|
||||
|
||||
wl_display_dispatch_pending(m_sWaylandState.display);
|
||||
wl_display_flush(m_sWaylandState.display);
|
||||
|
||||
m_sLoopState.wlDispatched = true;
|
||||
m_sLoopState.wlDispatchCV.notify_all();
|
||||
|
||||
if (pollfds[1].revents & POLLIN /* dbus */) {
|
||||
while (dbusConn && dbusConn->processPendingEvent()) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
if (pollfds[0].revents & POLLIN /* wl */) {
|
||||
Debug::log(TRACE, "got wl event");
|
||||
wl_display_flush(m_sWaylandState.display);
|
||||
if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
|
||||
wl_display_read_events(m_sWaylandState.display);
|
||||
wl_display_dispatch_pending(m_sWaylandState.display);
|
||||
} else {
|
||||
wl_display_dispatch(m_sWaylandState.display);
|
||||
}
|
||||
}
|
||||
|
||||
// finalize wayland dispatching. Dispatch pending on the queue
|
||||
int ret = 0;
|
||||
do {
|
||||
ret = wl_display_dispatch_pending(m_sWaylandState.display);
|
||||
wl_display_flush(m_sWaylandState.display);
|
||||
} while (ret > 0 && !m_bTerminate);
|
||||
|
||||
// do timers
|
||||
m_sLoopState.timersMutex.lock();
|
||||
auto timerscpy = m_vTimers;
|
||||
m_sLoopState.timersMutex.unlock();
|
||||
|
||||
std::vector<std::shared_ptr<CTimer>> passed;
|
||||
|
||||
for (auto& t : timerscpy) {
|
||||
if (t->passed() && !t->cancelled()) {
|
||||
t->call(t);
|
||||
passed.push_back(t);
|
||||
}
|
||||
|
||||
if (t->cancelled())
|
||||
passed.push_back(t);
|
||||
}
|
||||
|
||||
m_sLoopState.timersMutex.lock();
|
||||
std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); });
|
||||
m_sLoopState.timersMutex.unlock();
|
||||
|
||||
passed.clear();
|
||||
|
||||
if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) {
|
||||
releaseSessionLock();
|
||||
break;
|
||||
}
|
||||
processTimers();
|
||||
}
|
||||
|
||||
const auto DPY = m_sWaylandState.display;
|
||||
|
||||
m_sLoopState.timerEvent = true;
|
||||
m_sLoopState.timerCV.notify_all();
|
||||
g_pRenderer->asyncResourceGatherer->notify();
|
||||
g_pRenderer->asyncResourceGatherer->await();
|
||||
m_sWaylandState = {};
|
||||
dma = {};
|
||||
|
||||
m_vOutputs.clear();
|
||||
g_pEGL.reset();
|
||||
g_pRenderer.reset();
|
||||
g_pSeatManager.reset();
|
||||
g_asyncResourceManager.reset();
|
||||
g_pRenderer.reset();
|
||||
g_pEGL.reset();
|
||||
|
||||
wl_display_disconnect(DPY);
|
||||
|
||||
|
|
@ -529,21 +477,18 @@ void CHyprlock::run() {
|
|||
}
|
||||
|
||||
void CHyprlock::unlock() {
|
||||
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
|
||||
|
||||
if (**PNOFADEOUT || m_sCurrentDesktop != "Hyprland") {
|
||||
releaseSessionLock();
|
||||
if (!m_bLocked) {
|
||||
Debug::log(WARN, "Unlock called, but not locked yet. This can happen when dpms is off during the grace period.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_tFadeEnds = std::chrono::system_clock::now() + std::chrono::milliseconds(500);
|
||||
m_bFadeStarted = true;
|
||||
g_pRenderer->startFadeOut(true);
|
||||
|
||||
renderAllOutputs();
|
||||
}
|
||||
|
||||
bool CHyprlock::isUnlocked() {
|
||||
return m_bFadeStarted || m_bTerminate;
|
||||
return !m_bLocked;
|
||||
}
|
||||
|
||||
void CHyprlock::clearPasswordBuffer() {
|
||||
|
|
@ -556,25 +501,25 @@ void CHyprlock::clearPasswordBuffer() {
|
|||
}
|
||||
|
||||
void CHyprlock::renderOutput(const std::string& stringPort) {
|
||||
const auto MON = std::find_if(m_vOutputs.begin(), m_vOutputs.end(), [stringPort](const auto& other) { return other->stringPort == stringPort; });
|
||||
const auto MON = std::ranges::find_if(m_vOutputs, [stringPort](const auto& other) { return other->stringPort == stringPort; });
|
||||
|
||||
if (MON == m_vOutputs.end() || !MON->get())
|
||||
if (MON == m_vOutputs.end() || !*MON)
|
||||
return;
|
||||
|
||||
const auto PMONITOR = MON->get();
|
||||
const auto& PMONITOR = *MON;
|
||||
|
||||
if (!PMONITOR->sessionLockSurface)
|
||||
if (!PMONITOR->m_sessionLockSurface)
|
||||
return;
|
||||
|
||||
PMONITOR->sessionLockSurface->render();
|
||||
PMONITOR->m_sessionLockSurface->render();
|
||||
}
|
||||
|
||||
void CHyprlock::renderAllOutputs() {
|
||||
for (auto& o : m_vOutputs) {
|
||||
if (!o->sessionLockSurface)
|
||||
if (!o->m_sessionLockSurface)
|
||||
continue;
|
||||
|
||||
o->sessionLockSurface->render();
|
||||
o->m_sessionLockSurface->render();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -590,7 +535,7 @@ void CHyprlock::startKeyRepeat(xkb_keysym_t sym) {
|
|||
if (m_iKeebRepeatDelay <= 0)
|
||||
return;
|
||||
|
||||
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatDelay), [sym](std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
|
||||
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatDelay), [sym](ASP<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
|
||||
}
|
||||
|
||||
void CHyprlock::repeatKey(xkb_keysym_t sym) {
|
||||
|
|
@ -601,13 +546,13 @@ void CHyprlock::repeatKey(xkb_keysym_t sym) {
|
|||
|
||||
// This condition is for backspace and delete keys, but should also be ok for other keysyms since our buffer won't be empty anyways
|
||||
if (bool CONTINUE = m_sPasswordState.passBuffer.length() > 0; CONTINUE)
|
||||
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatRate), [sym](std::shared_ptr<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
|
||||
m_pKeyRepeatTimer = addTimer(std::chrono::milliseconds(m_iKeebRepeatRate), [sym](ASP<CTimer> self, void* data) { g_pHyprlock->repeatKey(sym); }, nullptr);
|
||||
|
||||
renderAllOutputs();
|
||||
}
|
||||
|
||||
void CHyprlock::onKey(uint32_t key, bool down) {
|
||||
if (m_bFadeStarted || m_bTerminate)
|
||||
if (isUnlocked())
|
||||
return;
|
||||
|
||||
if (down && std::chrono::system_clock::now() < m_tGraceEnds) {
|
||||
|
|
@ -615,10 +560,10 @@ void CHyprlock::onKey(uint32_t key, bool down) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (down && std::find(m_vPressedKeys.begin(), m_vPressedKeys.end(), key) != m_vPressedKeys.end()) {
|
||||
if (down && std::ranges::find(m_vPressedKeys, key) != m_vPressedKeys.end()) {
|
||||
Debug::log(ERR, "Invalid key down event (key already pressed?)");
|
||||
return;
|
||||
} else if (!down && std::find(m_vPressedKeys.begin(), m_vPressedKeys.end(), key) == m_vPressedKeys.end()) {
|
||||
} else if (!down && std::ranges::find(m_vPressedKeys, key) == m_vPressedKeys.end()) {
|
||||
Debug::log(ERR, "Invalid key down event (stray release event?)");
|
||||
return;
|
||||
}
|
||||
|
|
@ -638,6 +583,9 @@ void CHyprlock::onKey(uint32_t key, bool down) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (g_pAuth->m_bDisplayFailText)
|
||||
g_pAuth->resetDisplayFail();
|
||||
|
||||
if (down) {
|
||||
m_bCapsLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
|
||||
m_bNumLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED);
|
||||
|
|
@ -665,16 +613,16 @@ void CHyprlock::onKey(uint32_t key, bool down) {
|
|||
void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
|
||||
const auto SYM = sym;
|
||||
|
||||
if (SYM == XKB_KEY_Escape || (m_bCtrl && (SYM == XKB_KEY_u || SYM == XKB_KEY_BackSpace))) {
|
||||
if (SYM == XKB_KEY_Escape || (m_bCtrl && (SYM == XKB_KEY_u || SYM == XKB_KEY_BackSpace || SYM == XKB_KEY_a))) {
|
||||
Debug::log(LOG, "Clearing password buffer");
|
||||
|
||||
m_sPasswordState.passBuffer = "";
|
||||
} else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) {
|
||||
Debug::log(LOG, "Authenticating");
|
||||
|
||||
static auto* const PIGNOREEMPTY = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:ignore_empty_input");
|
||||
static const auto IGNOREEMPTY = g_pConfigManager->getValue<Hyprlang::INT>("general:ignore_empty_input");
|
||||
|
||||
if (m_sPasswordState.passBuffer.empty() && **PIGNOREEMPTY) {
|
||||
if (m_sPasswordState.passBuffer.empty() && *IGNOREEMPTY) {
|
||||
Debug::log(LOG, "Ignoring empty input");
|
||||
return;
|
||||
}
|
||||
|
|
@ -701,9 +649,71 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
|
|||
}
|
||||
}
|
||||
|
||||
void CHyprlock::acquireSessionLock() {
|
||||
void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
|
||||
if (!down)
|
||||
return;
|
||||
|
||||
if (!m_focusedOutput.lock())
|
||||
return;
|
||||
|
||||
// TODO: add the UNLIKELY marco from Hyprland
|
||||
if (!m_focusedOutput->m_sessionLockSurface)
|
||||
return;
|
||||
|
||||
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
|
||||
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
|
||||
for (const auto& widget : widgets) {
|
||||
if (widget->containsPoint(SCALEDPOS))
|
||||
widget->onClick(button, down, pos);
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprlock::onHover(const Vector2D& pos) {
|
||||
if (!m_focusedOutput.lock())
|
||||
return;
|
||||
|
||||
if (!m_focusedOutput->m_sessionLockSurface)
|
||||
return;
|
||||
|
||||
bool outputNeedsRedraw = false;
|
||||
bool cursorChanged = false;
|
||||
|
||||
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
|
||||
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
|
||||
for (const auto& widget : widgets) {
|
||||
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
|
||||
const bool HOVERED = widget->isHovered();
|
||||
|
||||
if (CONTAINSPOINT) {
|
||||
if (!HOVERED) {
|
||||
widget->setHover(true);
|
||||
widget->onHover(pos);
|
||||
outputNeedsRedraw = true;
|
||||
}
|
||||
|
||||
if (!cursorChanged)
|
||||
cursorChanged = true;
|
||||
|
||||
} else if (HOVERED) {
|
||||
widget->setHover(false);
|
||||
outputNeedsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cursorChanged)
|
||||
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
|
||||
|
||||
if (outputNeedsRedraw)
|
||||
m_focusedOutput->m_sessionLockSurface->render();
|
||||
}
|
||||
|
||||
bool CHyprlock::acquireSessionLock() {
|
||||
Debug::log(LOG, "Locking session");
|
||||
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
|
||||
if (!m_sLockState.lock) {
|
||||
Debug::log(ERR, "Failed to create a lock object!");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sLockState.lock->setLocked([this](CCExtSessionLockV1* r) { onLockLocked(); });
|
||||
|
||||
|
|
@ -711,10 +721,27 @@ void CHyprlock::acquireSessionLock() {
|
|||
|
||||
// roundtrip in case the compositor sends `finished` right away
|
||||
wl_display_roundtrip(m_sWaylandState.display);
|
||||
|
||||
// recieved finished right away (probably already locked)
|
||||
if (m_bTerminate)
|
||||
return false;
|
||||
|
||||
m_lockAquired = true;
|
||||
|
||||
// create a session lock surface for exiting outputs
|
||||
for (auto& o : m_vOutputs) {
|
||||
if (!o->done)
|
||||
continue;
|
||||
|
||||
o->createSessionLockSurface();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CHyprlock::releaseSessionLock() {
|
||||
Debug::log(LOG, "Unlocking session");
|
||||
|
||||
if (m_bTerminate) {
|
||||
Debug::log(ERR, "Unlock already happend?");
|
||||
return;
|
||||
|
|
@ -742,12 +769,6 @@ void CHyprlock::releaseSessionLock() {
|
|||
wl_display_roundtrip(m_sWaylandState.display);
|
||||
}
|
||||
|
||||
void CHyprlock::createSessionLockSurfaces() {
|
||||
for (auto& o : m_vOutputs) {
|
||||
o->sessionLockSurface = std::make_unique<CSessionLockSurface>(o.get());
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprlock::onLockLocked() {
|
||||
Debug::log(LOG, "onLockLocked called");
|
||||
|
||||
|
|
@ -807,23 +828,47 @@ size_t CHyprlock::getPasswordBufferDisplayLen() {
|
|||
return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; });
|
||||
}
|
||||
|
||||
std::shared_ptr<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data,
|
||||
bool force) {
|
||||
ASP<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data, bool force) {
|
||||
std::lock_guard<std::mutex> lg(m_sLoopState.timersMutex);
|
||||
const auto T = m_vTimers.emplace_back(std::make_shared<CTimer>(timeout, cb_, data, force));
|
||||
const auto T = m_vTimers.emplace_back(makeAtomicShared<CTimer>(timeout, cb_, data, force));
|
||||
m_sLoopState.timerEvent = true;
|
||||
m_sLoopState.timerCV.notify_all();
|
||||
return T;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<CTimer>> CHyprlock::getTimers() {
|
||||
void CHyprlock::processTimers() {
|
||||
// do timers
|
||||
m_sLoopState.timersMutex.lock();
|
||||
auto timerscpy = m_vTimers;
|
||||
m_sLoopState.timersMutex.unlock();
|
||||
|
||||
std::vector<ASP<CTimer>> passed;
|
||||
|
||||
for (auto& t : timerscpy) {
|
||||
if (t->passed() && !t->cancelled()) {
|
||||
t->call(t);
|
||||
passed.push_back(t);
|
||||
}
|
||||
|
||||
if (t->cancelled())
|
||||
passed.push_back(t);
|
||||
}
|
||||
|
||||
m_sLoopState.timersMutex.lock();
|
||||
std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); });
|
||||
m_sLoopState.timersMutex.unlock();
|
||||
|
||||
passed.clear();
|
||||
}
|
||||
|
||||
std::vector<ASP<CTimer>> CHyprlock::getTimers() {
|
||||
return m_vTimers;
|
||||
}
|
||||
|
||||
void CHyprlock::enqueueForceUpdateTimers() {
|
||||
addTimer(
|
||||
std::chrono::milliseconds(1),
|
||||
[](std::shared_ptr<CTimer> self, void* data) {
|
||||
[](ASP<CTimer> self, void* data) {
|
||||
for (auto& t : g_pHyprlock->getTimers()) {
|
||||
if (t->canForceUpdate()) {
|
||||
t->call(t);
|
||||
|
|
@ -834,69 +879,10 @@ void CHyprlock::enqueueForceUpdateTimers() {
|
|||
nullptr, false);
|
||||
}
|
||||
|
||||
std::string CHyprlock::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();
|
||||
}
|
||||
|
||||
SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
|
||||
return m_sWaylandState.screencopy;
|
||||
}
|
||||
|
||||
void CHyprlock::attemptRestoreOnDeath() {
|
||||
if (m_bTerminate || m_sCurrentDesktop != "Hyprland")
|
||||
return;
|
||||
|
||||
const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
|
||||
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!XDG_RUNTIME_DIR || !HIS)
|
||||
return;
|
||||
|
||||
// dirty hack
|
||||
uint64_t timeNowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(0)).count();
|
||||
|
||||
const auto LASTRESTARTPATH = std::string{XDG_RUNTIME_DIR} + "/.hyprlockrestart";
|
||||
|
||||
if (std::filesystem::exists(LASTRESTARTPATH)) {
|
||||
std::ifstream ifs(LASTRESTARTPATH);
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
uint64_t timeEncoded = 0;
|
||||
try {
|
||||
timeEncoded = std::stoull(content);
|
||||
} catch (std::exception& e) {
|
||||
// oops?
|
||||
ifs.close();
|
||||
std::filesystem::remove(LASTRESTARTPATH);
|
||||
return;
|
||||
}
|
||||
ifs.close();
|
||||
|
||||
if (timeNowMs - timeEncoded < 4000 /* 4s, seems reasonable */) {
|
||||
Debug::log(LOG, "Not restoring on death; less than 4s since last death");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream ofs(LASTRESTARTPATH, std::ios::trunc);
|
||||
ofs << timeNowMs;
|
||||
ofs.close();
|
||||
|
||||
if (m_bLocked && m_sLockState.lock) {
|
||||
m_sLockState.lock.reset();
|
||||
|
||||
// Destroy sessionLockSurfaces
|
||||
m_vOutputs.clear();
|
||||
}
|
||||
|
||||
spawnSync("hyprctl keyword misc:allow_session_lock_restore true");
|
||||
spawnSync("hyprctl dispatch exec \"hyprlock --immediate --immediate-render\"");
|
||||
SP<CCWlShm> CHyprlock::getShm() {
|
||||
return m_sWaylandState.shm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@
|
|||
#include "linux-dmabuf-v1.hpp"
|
||||
#include "viewporter.hpp"
|
||||
#include "Output.hpp"
|
||||
#include "Seat.hpp"
|
||||
#include "CursorShape.hpp"
|
||||
#include "Timer.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
|
|
@ -29,48 +26,41 @@ struct SDMABUFModifier {
|
|||
|
||||
class CHyprlock {
|
||||
public:
|
||||
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool noFadeIn);
|
||||
CHyprlock(const std::string& wlDisplay, const bool immediateRender, const int gracePeriod);
|
||||
~CHyprlock();
|
||||
|
||||
void run();
|
||||
void run();
|
||||
|
||||
void unlock();
|
||||
bool isUnlocked();
|
||||
void unlock();
|
||||
bool isUnlocked();
|
||||
|
||||
void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
|
||||
void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name);
|
||||
ASP<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data, bool force = false);
|
||||
void processTimers();
|
||||
|
||||
std::shared_ptr<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data,
|
||||
bool force = false);
|
||||
void enqueueForceUpdateTimers();
|
||||
|
||||
void enqueueForceUpdateTimers();
|
||||
void onLockLocked();
|
||||
void onLockFinished();
|
||||
|
||||
void onLockLocked();
|
||||
void onLockFinished();
|
||||
bool acquireSessionLock();
|
||||
void releaseSessionLock();
|
||||
|
||||
void acquireSessionLock();
|
||||
void releaseSessionLock();
|
||||
void onKey(uint32_t key, bool down);
|
||||
void onClick(uint32_t button, bool down, const Vector2D& pos);
|
||||
void onHover(const Vector2D& pos);
|
||||
void startKeyRepeat(xkb_keysym_t sym);
|
||||
void repeatKey(xkb_keysym_t sym);
|
||||
void handleKeySym(xkb_keysym_t sym, bool compose);
|
||||
void onPasswordCheckTimer();
|
||||
void clearPasswordBuffer();
|
||||
bool passwordCheckWaiting();
|
||||
std::optional<std::string> passwordLastFailReason();
|
||||
|
||||
void createSessionLockSurfaces();
|
||||
void renderOutput(const std::string& stringPort);
|
||||
void renderAllOutputs();
|
||||
|
||||
void attemptRestoreOnDeath();
|
||||
|
||||
std::string spawnSync(const std::string& cmd);
|
||||
|
||||
void onKey(uint32_t key, bool down);
|
||||
void startKeyRepeat(xkb_keysym_t sym);
|
||||
void repeatKey(xkb_keysym_t sym);
|
||||
void handleKeySym(xkb_keysym_t sym, bool compose);
|
||||
void onPasswordCheckTimer();
|
||||
void clearPasswordBuffer();
|
||||
bool passwordCheckWaiting();
|
||||
std::optional<std::string> passwordLastFailReason();
|
||||
|
||||
void renderOutput(const std::string& stringPort);
|
||||
void renderAllOutputs();
|
||||
|
||||
size_t getPasswordBufferLen();
|
||||
size_t getPasswordBufferDisplayLen();
|
||||
size_t getPasswordBufferLen();
|
||||
size_t getPasswordBufferDisplayLen();
|
||||
|
||||
SP<CCExtSessionLockManagerV1> getSessionLockMgr();
|
||||
SP<CCExtSessionLockV1> getSessionLock();
|
||||
|
|
@ -79,6 +69,7 @@ class CHyprlock {
|
|||
SP<CCWpFractionalScaleManagerV1> getFractionalMgr();
|
||||
SP<CCWpViewporter> getViewporter();
|
||||
SP<CCZwlrScreencopyManagerV1> getScreencopy();
|
||||
SP<CCWlShm> getShm();
|
||||
|
||||
int32_t m_iKeebRepeatRate = 25;
|
||||
int32_t m_iKeebRepeatDelay = 600;
|
||||
|
|
@ -87,28 +78,28 @@ class CHyprlock {
|
|||
|
||||
bool m_bTerminate = false;
|
||||
|
||||
bool m_bLocked = 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_bCapsLock = false;
|
||||
bool m_bNumLock = false;
|
||||
bool m_bCtrl = false;
|
||||
|
||||
bool m_bImmediateRender = false;
|
||||
|
||||
bool m_bNoFadeIn = 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;
|
||||
|
||||
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;
|
||||
Vector2D m_vMouseLocation = {};
|
||||
|
||||
std::vector<std::unique_ptr<COutput>> m_vOutputs;
|
||||
std::vector<std::shared_ptr<CTimer>> getTimers();
|
||||
ASP<CTimer> m_pKeyRepeatTimer = nullptr;
|
||||
|
||||
std::vector<SP<COutput>> m_vOutputs;
|
||||
std::vector<ASP<CTimer>> getTimers();
|
||||
|
||||
struct {
|
||||
SP<CCZwpLinuxDmabufV1> linuxDmabuf = nullptr;
|
||||
|
|
@ -125,6 +116,9 @@ class CHyprlock {
|
|||
} dma;
|
||||
gbm_device* createGBMDevice(drmDevice* dev);
|
||||
|
||||
void addDmabufListener();
|
||||
void removeDmabufListener();
|
||||
|
||||
private:
|
||||
struct {
|
||||
wl_display* display = nullptr;
|
||||
|
|
@ -134,10 +128,9 @@ class CHyprlock {
|
|||
SP<CCWpFractionalScaleManagerV1> fractional = nullptr;
|
||||
SP<CCWpViewporter> viewporter = nullptr;
|
||||
SP<CCZwlrScreencopyManagerV1> screencopy = nullptr;
|
||||
SP<CCWlShm> shm = nullptr;
|
||||
} m_sWaylandState;
|
||||
|
||||
void addDmabufListener();
|
||||
|
||||
struct {
|
||||
SP<CCExtSessionLockV1> lock = nullptr;
|
||||
} m_sLockState;
|
||||
|
|
@ -155,14 +148,17 @@ 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;
|
||||
|
||||
std::vector<std::shared_ptr<CTimer>> m_vTimers;
|
||||
std::vector<ASP<CTimer>> m_vTimers;
|
||||
|
||||
std::vector<uint32_t> m_vPressedKeys;
|
||||
std::vector<uint32_t> m_vPressedKeys;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHyprlock> g_pHyprlock;
|
||||
inline UP<CHyprlock> g_pHyprlock;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <hyprutils/memory/Atomic.hpp>
|
||||
#include <hyprgraphics/color/Color.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
using namespace Hyprgraphics;
|
||||
|
||||
using ResourceID = size_t;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
|
||||
#define ASP CAtomicSharedPointer
|
||||
#define AWP CAtomicWeakPointer
|
||||
|
||||
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 = UP<CAnimatedVariable<VarType>>;
|
||||
|
||||
template <Animable VarType>
|
||||
using PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
uint32_t CColor::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;
|
||||
}
|
||||
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,35 +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);
|
||||
|
||||
float r = 0, g = 0, b = 0, a = 1.f;
|
||||
CHyprColor();
|
||||
CHyprColor(float r, float g, float b, float a);
|
||||
CHyprColor(const Hyprgraphics::CColor& col, float a);
|
||||
CHyprColor(uint64_t);
|
||||
|
||||
// AR32
|
||||
uint32_t getAsHex() const;
|
||||
uint32_t getAsHex() const;
|
||||
Hyprgraphics::CColor::SSRGB asRGB() const;
|
||||
Hyprgraphics::CColor::SOkLab asOkLab() const;
|
||||
Hyprgraphics::CColor::SHSL asHSL() const;
|
||||
CHyprColor stripA() const;
|
||||
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,4 +51,4 @@ namespace Debug {
|
|||
std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...)));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fcntl.h>
|
||||
#include "MiscFunctions.hpp"
|
||||
#include "../helpers/Log.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);
|
||||
|
|
@ -13,10 +17,11 @@ 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);
|
||||
|
|
@ -54,11 +59,11 @@ int64_t configStringToInt(const std::string& VALUE) {
|
|||
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;
|
||||
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);
|
||||
return (RGBA >> 8) + (0x1000000 * (RGBA & 0xFF));
|
||||
}
|
||||
|
||||
throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values");
|
||||
|
|
@ -76,7 +81,7 @@ int64_t configStringToInt(const std::string& VALUE) {
|
|||
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;
|
||||
return (Hyprlang::INT)0xFF000000 + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b;
|
||||
} else if (VALUEWITHOUTFUNC.length() == 6) {
|
||||
return parseHex(VALUEWITHOUTFUNC) + 0xFF000000;
|
||||
}
|
||||
|
|
@ -97,4 +102,59 @@ int64_t configStringToInt(const std::string& VALUE) {
|
|||
} 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@
|
|||
|
||||
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);
|
||||
|
|
|
|||
51
src/main.cpp
51
src/main.cpp
|
|
@ -2,6 +2,7 @@
|
|||
#include "config/ConfigManager.hpp"
|
||||
#include "core/hyprlock.hpp"
|
||||
#include "helpers/Log.hpp"
|
||||
#include "core/AnimationManager.hpp"
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ void help() {
|
|||
" -q, --quiet - Disable logging\n"
|
||||
" -c FILE, --config FILE - Specify config file to use\n"
|
||||
" --display NAME - Specify the Wayland display to connect to\n"
|
||||
" --immediate - Lock immediately, ignoring any configured grace period\n"
|
||||
" --grace SECONDS - Set grace period in seconds before requiring authentication\n"
|
||||
" --immediate-render - Do not wait for resources before drawing the background\n"
|
||||
" --no-fade-in - Disable the fade-in animation when the lock screen appears\n"
|
||||
" -V, --version - Show version information\n"
|
||||
|
|
@ -28,12 +29,20 @@ std::optional<std::string> parseArg(const std::vector<std::string>& args, const
|
|||
}
|
||||
}
|
||||
|
||||
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 immediateRender = false;
|
||||
bool noFadeIn = false;
|
||||
int graceSeconds = 0;
|
||||
|
||||
std::vector<std::string> args(argv, argv + argc);
|
||||
|
||||
|
|
@ -46,12 +55,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
}
|
||||
|
||||
if (arg == "--version" || arg == "-V") {
|
||||
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);
|
||||
|
||||
printVersion();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -73,8 +77,25 @@ int main(int argc, char** argv, char** envp) {
|
|||
else
|
||||
return 1;
|
||||
|
||||
} else if (arg == "--immediate")
|
||||
immediate = true;
|
||||
} else if (arg == "--grace" && i + 1 < (std::size_t)argc) {
|
||||
if (auto value = parseArg(args, arg, i); value) {
|
||||
try {
|
||||
graceSeconds = std::stoi(*value);
|
||||
if (graceSeconds < 0) {
|
||||
std::println(stderr, "Error: Grace period must be non-negative.");
|
||||
return 1;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
std::println(stderr, "Error: Invalid grace period value: {}", *value);
|
||||
return 1;
|
||||
}
|
||||
} else
|
||||
return 1;
|
||||
|
||||
} else if (arg == "--immediate") {
|
||||
graceSeconds = 0;
|
||||
Debug::log(WARN, R"("--immediate" is deprecated. Use the "--grace" option instead.)");
|
||||
}
|
||||
|
||||
else if (arg == "--immediate-render")
|
||||
immediateRender = true;
|
||||
|
|
@ -89,8 +110,11 @@ int main(int argc, char** argv, char** envp) {
|
|||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
|
@ -100,8 +124,11 @@ int main(int argc, char** argv, char** envp) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (noFadeIn)
|
||||
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
|
||||
|
||||
try {
|
||||
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender, noFadeIn);
|
||||
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediateRender, graceSeconds);
|
||||
g_pHyprlock->run();
|
||||
} catch (const std::exception& ex) {
|
||||
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());
|
||||
|
|
|
|||
|
|
@ -1,382 +0,0 @@
|
|||
#include "AsyncResourceGatherer.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include <cairo/cairo.h>
|
||||
#include <magic.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "src/helpers/Color.hpp"
|
||||
#include "src/helpers/Log.hpp"
|
||||
#include <hyprgraphics/image/Image.hpp>
|
||||
using namespace Hyprgraphics;
|
||||
|
||||
CAsyncResourceGatherer::CAsyncResourceGatherer() {
|
||||
if (g_pHyprlock->getScreencopy())
|
||||
enqueueDMAFrames();
|
||||
|
||||
initialGatherThread = std::thread([this]() { this->gather(); });
|
||||
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::enqueueDMAFrames() {
|
||||
// some things can't be done async :(
|
||||
// gather background textures when needed
|
||||
|
||||
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
|
||||
|
||||
std::vector<std::string> mons;
|
||||
|
||||
for (auto& c : CWIDGETS) {
|
||||
if (c.type != "background")
|
||||
continue;
|
||||
|
||||
if (std::string{std::any_cast<Hyprlang::STRING>(c.values.at("path"))} != "screenshot")
|
||||
continue;
|
||||
|
||||
// mamma mia
|
||||
if (c.monitor.empty()) {
|
||||
mons.clear();
|
||||
for (auto& m : g_pHyprlock->m_vOutputs) {
|
||||
mons.push_back(m->stringPort);
|
||||
}
|
||||
break;
|
||||
} else
|
||||
mons.push_back(c.monitor);
|
||||
}
|
||||
|
||||
for (auto& mon : mons) {
|
||||
const auto MON = std::find_if(g_pHyprlock->m_vOutputs.begin(), g_pHyprlock->m_vOutputs.end(),
|
||||
[mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
|
||||
|
||||
if (MON == g_pHyprlock->m_vOutputs.end())
|
||||
continue;
|
||||
|
||||
const auto PMONITOR = MON->get();
|
||||
|
||||
dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR));
|
||||
}
|
||||
}
|
||||
|
||||
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
|
||||
for (auto& a : assets) {
|
||||
if (a.first == id)
|
||||
return &a.second;
|
||||
}
|
||||
|
||||
if (apply()) {
|
||||
for (auto& a : assets) {
|
||||
if (a.first == id)
|
||||
return &a.second;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& dma : dmas) {
|
||||
if (id == dma->resourceID)
|
||||
return dma->asset.ready ? &dma->asset : nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) {
|
||||
|
||||
auto image = CImage(path);
|
||||
if (!image.success()) {
|
||||
Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return image.cairoSurface();
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::gather() {
|
||||
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
|
||||
|
||||
g_pEGL->makeCurrent(nullptr);
|
||||
|
||||
// gather resources to preload
|
||||
// clang-format off
|
||||
int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) {
|
||||
return w.type == "background" || w.type == "image";
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
progress = 0;
|
||||
for (auto& c : CWIDGETS) {
|
||||
if (c.type == "background" || c.type == "image") {
|
||||
#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100
|
||||
progress = progress + 1.0 / (preloads + 1.0);
|
||||
#else
|
||||
progress += 1.0 / (preloads + 1.0);
|
||||
#endif
|
||||
|
||||
std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
|
||||
|
||||
if (path.empty() || path == "screenshot")
|
||||
continue;
|
||||
|
||||
std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path;
|
||||
|
||||
// render the image directly, since we are in a seperate thread
|
||||
CAsyncResourceGatherer::SPreloadRequest rq;
|
||||
rq.type = CAsyncResourceGatherer::TARGET_IMAGE;
|
||||
rq.asset = path;
|
||||
rq.id = id;
|
||||
|
||||
renderImage(rq);
|
||||
}
|
||||
}
|
||||
|
||||
while (!g_pHyprlock->m_bTerminate && std::any_of(dmas.begin(), dmas.end(), [](const auto& d) { return !d->asset.ready; })) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
gathered = true;
|
||||
}
|
||||
|
||||
bool CAsyncResourceGatherer::apply() {
|
||||
preloadTargetsMutex.lock();
|
||||
|
||||
if (preloadTargets.empty()) {
|
||||
preloadTargetsMutex.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto currentPreloadTargets = preloadTargets;
|
||||
preloadTargets.clear();
|
||||
preloadTargetsMutex.unlock();
|
||||
|
||||
for (auto& t : currentPreloadTargets) {
|
||||
if (t.type == TARGET_IMAGE) {
|
||||
const auto ASSET = &assets[t.id];
|
||||
|
||||
const cairo_status_t SURFACESTATUS = (cairo_status_t)t.cairosurface->status();
|
||||
const auto CAIROFORMAT = cairo_image_surface_get_format(t.cairosurface->cairo());
|
||||
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
|
||||
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
|
||||
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
|
||||
|
||||
if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS));
|
||||
ASSET->texture.m_iType = TEXTURE_INVALID;
|
||||
}
|
||||
|
||||
ASSET->texture.m_vSize = t.size;
|
||||
ASSET->texture.allocate();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
|
||||
|
||||
cairo_destroy((cairo_t*)t.cairo);
|
||||
t.cairosurface.reset();
|
||||
} else
|
||||
Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
|
||||
SPreloadTarget target;
|
||||
target.type = TARGET_IMAGE;
|
||||
target.id = rq.id;
|
||||
|
||||
std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, ""));
|
||||
const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH);
|
||||
|
||||
if (!CAIROISURFACE) {
|
||||
Debug::log(ERR, "renderImage: No cairo surface!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto CAIRO = cairo_create(CAIROISURFACE->cairo());
|
||||
cairo_scale(CAIRO, 1, 1);
|
||||
|
||||
target.cairo = CAIRO;
|
||||
target.cairosurface = CAIROISURFACE;
|
||||
target.data = CAIROISURFACE->data();
|
||||
target.size = CAIROISURFACE->size();
|
||||
|
||||
std::lock_guard lg{preloadTargetsMutex};
|
||||
preloadTargets.push_back(target);
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
|
||||
SPreloadTarget target;
|
||||
target.type = TARGET_IMAGE; /* text is just an image lol */
|
||||
target.id = rq.id;
|
||||
|
||||
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
|
||||
const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CColor>(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0);
|
||||
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans";
|
||||
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
|
||||
|
||||
static auto* const TRIM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:text_trim");
|
||||
std::string text = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
|
||||
|
||||
if (**TRIM) {
|
||||
text.erase(0, text.find_first_not_of(" \n\r\t"));
|
||||
text.erase(text.find_last_not_of(" \n\r\t") + 1);
|
||||
}
|
||||
|
||||
auto CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */));
|
||||
auto CAIRO = cairo_create(CAIROSURFACE->cairo());
|
||||
|
||||
// draw title using Pango
|
||||
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
|
||||
|
||||
PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str());
|
||||
pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE);
|
||||
pango_layout_set_font_description(layout, fontDesc);
|
||||
pango_font_description_free(fontDesc);
|
||||
|
||||
if (rq.props.contains("text_align")) {
|
||||
const std::string TEXTALIGN = std::any_cast<std::string>(rq.props.at("text_align"));
|
||||
PangoAlignment align = PANGO_ALIGN_LEFT;
|
||||
if (TEXTALIGN == "center")
|
||||
align = PANGO_ALIGN_CENTER;
|
||||
else if (TEXTALIGN == "right")
|
||||
align = PANGO_ALIGN_RIGHT;
|
||||
|
||||
pango_layout_set_alignment(layout, align);
|
||||
}
|
||||
|
||||
PangoAttrList* attrList = nullptr;
|
||||
GError* gError = nullptr;
|
||||
char* buf = nullptr;
|
||||
if (pango_parse_markup(text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
|
||||
pango_layout_set_text(layout, buf, -1);
|
||||
else {
|
||||
Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message);
|
||||
g_error_free(gError);
|
||||
pango_layout_set_text(layout, text.c_str(), -1);
|
||||
}
|
||||
|
||||
if (!attrList)
|
||||
attrList = pango_attr_list_new();
|
||||
|
||||
if (buf)
|
||||
free(buf);
|
||||
|
||||
pango_attr_list_insert(attrList, pango_attr_scale_new(1));
|
||||
pango_layout_set_attributes(layout, attrList);
|
||||
pango_attr_list_unref(attrList);
|
||||
|
||||
int layoutWidth, layoutHeight;
|
||||
pango_layout_get_size(layout, &layoutWidth, &layoutHeight);
|
||||
|
||||
// TODO: avoid this?
|
||||
cairo_destroy(CAIRO);
|
||||
CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE));
|
||||
CAIRO = cairo_create(CAIROSURFACE->cairo());
|
||||
|
||||
// clear the pixmap
|
||||
cairo_save(CAIRO);
|
||||
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
|
||||
cairo_paint(CAIRO);
|
||||
cairo_restore(CAIRO);
|
||||
|
||||
// render the thing
|
||||
cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a);
|
||||
|
||||
cairo_move_to(CAIRO, 0, 0);
|
||||
pango_cairo_show_layout(CAIRO, layout);
|
||||
|
||||
g_object_unref(layout);
|
||||
|
||||
cairo_surface_flush(CAIROSURFACE->cairo());
|
||||
|
||||
target.cairo = CAIRO;
|
||||
target.cairosurface = CAIROSURFACE;
|
||||
target.data = CAIROSURFACE->data();
|
||||
target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE};
|
||||
|
||||
std::lock_guard lg{preloadTargetsMutex};
|
||||
preloadTargets.push_back(target);
|
||||
}
|
||||
|
||||
struct STimerCallbackData {
|
||||
void (*cb)(void*) = nullptr;
|
||||
void* data = nullptr;
|
||||
};
|
||||
|
||||
static void timerCallback(std::shared_ptr<CTimer> self, void* data_) {
|
||||
auto data = (STimerCallbackData*)data_;
|
||||
data->cb(data->data);
|
||||
delete data;
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::asyncAssetSpinLock() {
|
||||
while (!g_pHyprlock->m_bTerminate) {
|
||||
|
||||
std::unique_lock lk(asyncLoopState.requestsMutex);
|
||||
if (asyncLoopState.pending == false) // avoid a lock if a thread managed to request something already since we .unlock()ed
|
||||
asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events
|
||||
|
||||
asyncLoopState.pending = false;
|
||||
|
||||
if (asyncLoopState.requests.empty()) {
|
||||
lk.unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto requests = asyncLoopState.requests;
|
||||
asyncLoopState.requests.clear();
|
||||
|
||||
lk.unlock();
|
||||
|
||||
// process requests
|
||||
for (auto& r : requests) {
|
||||
Debug::log(TRACE, "Processing requested resourceID {}", r.id);
|
||||
|
||||
if (r.type == TARGET_TEXT) {
|
||||
renderText(r);
|
||||
} else if (r.type == TARGET_IMAGE) {
|
||||
renderImage(r);
|
||||
} else {
|
||||
Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type);
|
||||
continue;
|
||||
}
|
||||
|
||||
// plant timer for callback
|
||||
if (r.callback)
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), timerCallback, new STimerCallbackData{r.callback, r.callbackData});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) {
|
||||
Debug::log(TRACE, "Requesting label resource {}", request.id);
|
||||
|
||||
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
|
||||
asyncLoopState.requests.push_back(request);
|
||||
asyncLoopState.pending = true;
|
||||
asyncLoopState.requestsCV.notify_all();
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* asset) {
|
||||
std::erase_if(assets, [asset](const auto& a) { return &a.second == asset; });
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::notify() {
|
||||
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
|
||||
asyncLoopState.requests.clear();
|
||||
asyncLoopState.pending = true;
|
||||
asyncLoopState.requestsCV.notify_all();
|
||||
}
|
||||
|
||||
void CAsyncResourceGatherer::await() {
|
||||
if (initialGatherThread.joinable())
|
||||
initialGatherThread.join();
|
||||
if (asyncLoopThread.joinable())
|
||||
asyncLoopThread.join();
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "DMAFrame.hpp"
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <condition_variable>
|
||||
#include <any>
|
||||
#include "Shared.hpp"
|
||||
#include <hyprgraphics/cairo/CairoSurface.hpp>
|
||||
|
||||
class CAsyncResourceGatherer {
|
||||
public:
|
||||
CAsyncResourceGatherer();
|
||||
std::atomic<bool> gathered = false;
|
||||
|
||||
std::atomic<float> progress = 0;
|
||||
|
||||
/* only call from ogl thread */
|
||||
SPreloadedAsset* getAssetByID(const std::string& id);
|
||||
|
||||
bool apply();
|
||||
|
||||
enum eTargetType {
|
||||
TARGET_IMAGE = 0,
|
||||
TARGET_TEXT
|
||||
};
|
||||
|
||||
struct SPreloadRequest {
|
||||
eTargetType type;
|
||||
std::string asset;
|
||||
std::string id;
|
||||
|
||||
std::unordered_map<std::string, std::any> props;
|
||||
|
||||
// optional. Callbacks will be dispatched from the main thread,
|
||||
// so wayland/gl calls are OK.
|
||||
// will fire once the resource is fully loaded and ready.
|
||||
void (*callback)(void*) = nullptr;
|
||||
void* callbackData = nullptr;
|
||||
};
|
||||
|
||||
void requestAsyncAssetPreload(const SPreloadRequest& request);
|
||||
void unloadAsset(SPreloadedAsset* asset);
|
||||
void notify();
|
||||
void await();
|
||||
|
||||
private:
|
||||
std::thread asyncLoopThread;
|
||||
std::thread initialGatherThread;
|
||||
|
||||
void asyncAssetSpinLock();
|
||||
void renderText(const SPreloadRequest& rq);
|
||||
void renderImage(const SPreloadRequest& rq);
|
||||
|
||||
struct {
|
||||
std::condition_variable requestsCV;
|
||||
std::mutex requestsMutex;
|
||||
|
||||
std::vector<SPreloadRequest> requests;
|
||||
bool pending = false;
|
||||
|
||||
bool busy = false;
|
||||
} asyncLoopState;
|
||||
|
||||
struct SPreloadTarget {
|
||||
eTargetType type = TARGET_IMAGE;
|
||||
std::string id = "";
|
||||
|
||||
void* data = nullptr;
|
||||
void* cairo = nullptr;
|
||||
SP<Hyprgraphics::CCairoSurface> cairosurface;
|
||||
|
||||
Vector2D size;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<CDMAFrame>> dmas;
|
||||
|
||||
std::vector<SPreloadTarget> preloadTargets;
|
||||
std::mutex preloadTargetsMutex;
|
||||
|
||||
std::unordered_map<std::string, SPreloadedAsset> assets;
|
||||
|
||||
void gather();
|
||||
void enqueueDMAFrames();
|
||||
};
|
||||
351
src/renderer/AsyncResourceManager.cpp
Normal file
351
src/renderer/AsyncResourceManager.cpp
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
#include "AsyncResourceManager.hpp"
|
||||
|
||||
#include "./resources/TextCmdResource.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/poll.h>
|
||||
|
||||
using namespace Hyprgraphics;
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
static inline ResourceID scopeResourceID(uint8_t scope, size_t in) {
|
||||
return (in & ~0x0f) | scope;
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource::STextResourceData& s) {
|
||||
// TODO: Currently ignores the font string and resulting distribution is probably not perfect.
|
||||
const auto H1 = std::hash<std::string>{}(s.text);
|
||||
const auto H2 = std::hash<double>{}(s.color.asRgb().r);
|
||||
const auto H3 = std::hash<double>{}(s.color.asRgb().g) + s.fontSize;
|
||||
const auto H4 = std::hash<double>{}(s.color.asRgb().b) + s.align;
|
||||
|
||||
return scopeResourceID(1, H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3));
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision) {
|
||||
return scopeResourceID(2, resourceIDForTextRequest(s) ^ (revision << 32));
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::resourceIDForImageRequest(const std::string& path, size_t revision) {
|
||||
return scopeResourceID(3, std::hash<std::string>{}(path) ^ (revision << 32));
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& port) {
|
||||
return scopeResourceID(4, std::hash<std::string>{}(port));
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP<IWidget>& widget) {
|
||||
const auto RESOURCEID = resourceIDForTextRequest(params);
|
||||
if (request(RESOURCEID, widget)) {
|
||||
Debug::log(TRACE, "Reusing text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get());
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
auto resource = makeAtomicShared<CTextResource>(CTextResource::STextResourceData{params});
|
||||
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
|
||||
|
||||
Debug::log(TRACE, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get());
|
||||
enqueue(RESOURCEID, resourceGeneric, widget);
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP<IWidget>& widget) {
|
||||
const auto RESOURCEID = resourceIDForTextCmdRequest(params, revision);
|
||||
if (request(RESOURCEID, widget)) {
|
||||
Debug::log(TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get());
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
auto resource = makeAtomicShared<CTextCmdResource>(CTextResource::STextResourceData{params});
|
||||
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
|
||||
|
||||
Debug::log(TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get());
|
||||
enqueue(RESOURCEID, resourceGeneric, widget);
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP<IWidget>& widget) {
|
||||
const auto RESOURCEID = resourceIDForImageRequest(path, revision);
|
||||
if (request(RESOURCEID, widget)) {
|
||||
Debug::log(TRACE, "Reusing image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get());
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
auto resource = makeAtomicShared<CImageResource>(absolutePath(path, ""));
|
||||
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
|
||||
|
||||
Debug::log(TRACE, "Requesting image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get());
|
||||
enqueue(RESOURCEID, resourceGeneric, widget);
|
||||
return RESOURCEID;
|
||||
}
|
||||
|
||||
ASP<CTexture> CAsyncResourceManager::getAssetByID(size_t id) {
|
||||
if (!m_assets.contains(id))
|
||||
return nullptr;
|
||||
|
||||
return m_assets[id].texture;
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::enqueueStaticAssets() {
|
||||
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
|
||||
|
||||
for (auto& c : CWIDGETS) {
|
||||
if (c.type == "background" || c.type == "image") {
|
||||
std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
|
||||
|
||||
if (path.empty() || path == "screenshot")
|
||||
continue;
|
||||
|
||||
requestImage(path, 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::enqueueScreencopyFrames() {
|
||||
if (g_pHyprlock->m_vOutputs.empty())
|
||||
return;
|
||||
|
||||
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
|
||||
|
||||
const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn");
|
||||
const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut");
|
||||
|
||||
const bool FADENEEDSSC = *ANIMATIONSENABLED &&
|
||||
((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled
|
||||
(FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled));
|
||||
|
||||
const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { //
|
||||
return w.type == "background" && std::string{std::any_cast<Hyprlang::STRING>(w.values.at("path"))} == "screenshot";
|
||||
});
|
||||
|
||||
if (!BGSCREENSHOT && !FADENEEDSSC) {
|
||||
Debug::log(LOG, "Skipping screencopy");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& MON : g_pHyprlock->m_vOutputs) {
|
||||
m_scFrames.emplace_back(makeUnique<CScreencopyFrame>());
|
||||
auto* frame = m_scFrames.back().get();
|
||||
frame->capture(MON);
|
||||
m_assets.emplace(frame->m_resourceID, SPreloadedTexture{.texture = nullptr, .refs = 1});
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) {
|
||||
if (!scFrame.m_ready || !m_assets.contains(scFrame.m_resourceID)) {
|
||||
Debug::log(ERR, "Bogus call to CAsyncResourceManager::screencopyToTexture. This is a bug!");
|
||||
return;
|
||||
}
|
||||
|
||||
m_assets[scFrame.m_resourceID].texture = scFrame.m_asset;
|
||||
|
||||
Debug::log(TRACE, "Done sc frame {}", scFrame.m_resourceID);
|
||||
|
||||
std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; });
|
||||
|
||||
if (m_scFrames.empty()) {
|
||||
Debug::log(LOG, "Gathered all screencopy frames - removing dmabuf listeners");
|
||||
g_pHyprlock->removeDmabufListener();
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::gatherInitialResources(wl_display* display) {
|
||||
const auto MAXDELAYMS = 2000; // 2 Seconds
|
||||
const auto STARTGATHERTP = std::chrono::system_clock::now();
|
||||
|
||||
m_gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)};
|
||||
|
||||
int fdcount = 1;
|
||||
pollfd pollfds[2];
|
||||
pollfds[0] = {
|
||||
.fd = wl_display_get_fd(display),
|
||||
.events = POLLIN,
|
||||
};
|
||||
|
||||
if (m_gatheredEventfd.isValid()) {
|
||||
pollfds[1] = {
|
||||
.fd = m_gatheredEventfd.get(),
|
||||
.events = POLLIN,
|
||||
};
|
||||
|
||||
fdcount++;
|
||||
}
|
||||
|
||||
bool gathered = false;
|
||||
while (!gathered) {
|
||||
wl_display_flush(display);
|
||||
if (wl_display_prepare_read(display) == 0) {
|
||||
if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) {
|
||||
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
|
||||
wl_display_cancel_read(display);
|
||||
continue;
|
||||
}
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
wl_display_dispatch(display);
|
||||
}
|
||||
|
||||
g_pHyprlock->processTimers();
|
||||
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) {
|
||||
Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS);
|
||||
break;
|
||||
}
|
||||
|
||||
gathered = m_resources.empty() && m_scFrames.empty();
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Resources gathered after {} milliseconds", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count());
|
||||
}
|
||||
|
||||
bool CAsyncResourceManager::checkIdPresent(ResourceID id) {
|
||||
return m_assets.contains(id);
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::unload(ASP<CTexture> texture) {
|
||||
auto preload = std::ranges::find_if(m_assets, [texture](const auto& a) { return a.second.texture == texture; });
|
||||
if (preload == m_assets.end())
|
||||
return;
|
||||
|
||||
preload->second.refs--;
|
||||
|
||||
if (preload->second.refs == 0) {
|
||||
Debug::log(TRACE, "Releasing resourceID: {}!", preload->first);
|
||||
m_assets.erase(preload->first);
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::unloadById(ResourceID id) {
|
||||
if (!m_assets.contains(id))
|
||||
return;
|
||||
|
||||
m_assets[id].refs--;
|
||||
|
||||
if (m_assets[id].refs == 0) {
|
||||
Debug::log(TRACE, "Releasing resourceID: {}!", id);
|
||||
m_assets.erase(id);
|
||||
}
|
||||
}
|
||||
|
||||
bool CAsyncResourceManager::request(ResourceID id, const AWP<IWidget>& widget) {
|
||||
if (!m_assets.contains(id)) {
|
||||
// New asset!!
|
||||
m_assets.emplace(id, SPreloadedTexture{.texture = nullptr, .refs = 1});
|
||||
return false;
|
||||
}
|
||||
|
||||
m_assets[id].refs++;
|
||||
|
||||
if (m_assets[id].texture) {
|
||||
// Asset already present. Dispatch the asset callback function.
|
||||
const auto& TEXTURE = m_assets[id].texture;
|
||||
if (widget)
|
||||
widget->onAssetUpdate(id, TEXTURE);
|
||||
|
||||
// TODO: add a centalized mechanism to render in one place in the event loop to avoid duplicate render calls
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->renderAllOutputs(); }, nullptr);
|
||||
} else if (widget) {
|
||||
// Asset currently in-flight. Add the widget reference to in order for the callback to get dispatched later.
|
||||
m_resourcesMutex.lock();
|
||||
if (!m_resources.contains(id)) {
|
||||
Debug::log(ERR, "In-flight resourceID: {} not found! This is a bug.", id);
|
||||
m_resourcesMutex.unlock();
|
||||
return true;
|
||||
}
|
||||
m_resources[id].second.emplace_back(widget);
|
||||
m_resourcesMutex.unlock();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::enqueue(ResourceID resourceID, const ASP<IAsyncResource>& resource, const AWP<IWidget>& widget) {
|
||||
m_gatherer.enqueue(resource);
|
||||
|
||||
m_resourcesMutex.lock();
|
||||
if (m_resources.contains(resourceID))
|
||||
Debug::log(ERR, "Resource already enqueued! This is a bug.");
|
||||
|
||||
m_resources[resourceID] = {resource, {widget}};
|
||||
m_resourcesMutex.unlock();
|
||||
|
||||
resource->m_events.finished.listenStatic([resourceID]() {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, void* resourceID) { g_asyncResourceManager->onResourceFinished((size_t)resourceID); }, (void*)resourceID);
|
||||
});
|
||||
}
|
||||
|
||||
void CAsyncResourceManager::onResourceFinished(ResourceID id) {
|
||||
m_resourcesMutex.lock();
|
||||
if (!m_resources.contains(id)) {
|
||||
m_resourcesMutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto RESOURCE = m_resources[id].first;
|
||||
const auto WIDGETS = m_resources[id].second;
|
||||
m_resources.erase(id);
|
||||
m_resourcesMutex.unlock();
|
||||
|
||||
if (!m_assets.contains(id) || m_assets[id].refs == 0) // Not referenced? Drop it
|
||||
return;
|
||||
|
||||
if (!RESOURCE || !RESOURCE->m_asset.cairoSurface)
|
||||
return;
|
||||
|
||||
Debug::log(TRACE, "Resource to texture id:{}", id);
|
||||
|
||||
const auto texture = makeAtomicShared<CTexture>();
|
||||
|
||||
const cairo_status_t SURFACESTATUS = (cairo_status_t)RESOURCE->m_asset.cairoSurface->status();
|
||||
const auto CAIROFORMAT = cairo_image_surface_get_format(RESOURCE->m_asset.cairoSurface->cairo());
|
||||
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
|
||||
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
|
||||
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
|
||||
|
||||
if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(ERR, "resourceID: {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS));
|
||||
texture->m_iType = TEXTURE_INVALID;
|
||||
}
|
||||
|
||||
texture->m_vSize = RESOURCE->m_asset.pixelSize;
|
||||
texture->allocate();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
}
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data());
|
||||
|
||||
m_assets[id].texture = texture;
|
||||
|
||||
for (const auto& widget : WIDGETS) {
|
||||
if (widget)
|
||||
widget->onAssetUpdate(id, texture);
|
||||
}
|
||||
|
||||
g_pHyprlock->renderAllOutputs();
|
||||
|
||||
if (!m_gathered && !g_pHyprlock->m_bImmediateRender) {
|
||||
m_resourcesMutex.lock();
|
||||
if (m_resources.empty()) {
|
||||
m_gathered = true;
|
||||
if (m_gatheredEventfd.isValid())
|
||||
eventfd_write(m_gatheredEventfd.get(), 1);
|
||||
|
||||
m_gatheredEventfd.reset();
|
||||
}
|
||||
m_resourcesMutex.unlock();
|
||||
}
|
||||
}
|
||||
91
src/renderer/AsyncResourceManager.hpp
Normal file
91
src/renderer/AsyncResourceManager.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "./Texture.hpp"
|
||||
#include "./Screencopy.hpp"
|
||||
#include "./widgets/IWidget.hpp"
|
||||
|
||||
#include <hyprgraphics/resource/AsyncResourceGatherer.hpp>
|
||||
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
#include <hyprgraphics/resource/resources/ImageResource.hpp>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
class CAsyncResourceManager {
|
||||
|
||||
public:
|
||||
// Notes on resource lifetimes:
|
||||
// Resources id's are the result of hashing the requested resource parameters.
|
||||
// When a new request is made, adding a new entry to the m_assets map is done immediatly.
|
||||
// Subsequent calls through this section with the same resource id will increment the texture's references.
|
||||
// The manager will release the resource when refs reaches 0, while the resource itelf may outlife it's reference in the manager.
|
||||
// Why not use ASP/AWP for this?
|
||||
// The problem is that we want to to increment the reference as soon as requesting the resource id.
|
||||
// Not only when actually retrieving the asset with `getAssetById`.
|
||||
//
|
||||
// Improvement idea: Make a wrapper object that is returned when requesting and contains the resource id. Then we can unload with RAII.
|
||||
|
||||
// Those are hash functions that return the id for a requested resource.
|
||||
static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s);
|
||||
// Consumer needs to increment the revision parameter to get a new command evaluation.
|
||||
static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision);
|
||||
// Image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id.
|
||||
static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision);
|
||||
static ResourceID resourceIDForScreencopy(const std::string& port);
|
||||
|
||||
struct SPreloadedTexture {
|
||||
ASP<CTexture> texture;
|
||||
size_t refs = 0;
|
||||
};
|
||||
|
||||
CAsyncResourceManager() = default;
|
||||
~CAsyncResourceManager() = default;
|
||||
|
||||
ResourceID requestText(const CTextResource::STextResourceData& params, const AWP<IWidget>& widget);
|
||||
// Same as requestText but substitute the text with what launching sh -c request.text returns.
|
||||
ResourceID requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP<IWidget>& widget);
|
||||
ResourceID requestImage(const std::string& path, size_t revision, const AWP<IWidget>& widget);
|
||||
|
||||
ASP<CTexture> getAssetByID(ResourceID id);
|
||||
|
||||
void unload(ASP<CTexture> resource);
|
||||
void unloadById(ResourceID id);
|
||||
|
||||
void enqueueStaticAssets();
|
||||
void enqueueScreencopyFrames();
|
||||
void screencopyToTexture(const CScreencopyFrame& scFrame);
|
||||
void gatherInitialResources(wl_display* display);
|
||||
|
||||
bool checkIdPresent(ResourceID id);
|
||||
|
||||
private:
|
||||
// Returns whether or not the id was already requested.
|
||||
// Makes sure the widgets onAssetCallback function gets called.
|
||||
bool request(ResourceID id, const AWP<IWidget>& widget);
|
||||
// Adds a new resource to m_resources and passes it to m_gatherer.
|
||||
void enqueue(ResourceID resourceID, const ASP<IAsyncResource>& resource, const AWP<IWidget>& widget);
|
||||
// Callback for finished resources.
|
||||
// Copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map.
|
||||
// Removes the entry in m_resources.
|
||||
// Call onAssetUpdate for all stored widget references.
|
||||
void onResourceFinished(ResourceID id);
|
||||
|
||||
// For polling when using gatherInitialResources.
|
||||
bool m_gathered = false;
|
||||
Hyprutils::OS::CFileDescriptor m_gatheredEventfd;
|
||||
|
||||
bool m_exit = false;
|
||||
|
||||
int m_loadedAssets = 0;
|
||||
|
||||
// not shared between threads
|
||||
std::unordered_map<ResourceID, SPreloadedTexture> m_assets;
|
||||
std::vector<UP<CScreencopyFrame>> m_scFrames;
|
||||
// shared between threads
|
||||
std::mutex m_resourcesMutex;
|
||||
std::unordered_map<ResourceID, std::pair<ASP<Hyprgraphics::IAsyncResource>, std::vector<AWP<IWidget>>>> m_resources;
|
||||
|
||||
Hyprgraphics::CAsyncResourceGatherer m_gatherer;
|
||||
};
|
||||
|
||||
inline UP<CAsyncResourceManager> g_asyncResourceManager;
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
#include "DMAFrame.hpp"
|
||||
#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;
|
||||
|
||||
//
|
||||
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 = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, output_->output->resource()));
|
||||
|
||||
frameCb->setBufferDone([this](CCZwlrScreencopyFrameV1* r) {
|
||||
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this);
|
||||
|
||||
if (!onBufferDone()) {
|
||||
Debug::log(ERR, "onBufferDone failed");
|
||||
return;
|
||||
}
|
||||
|
||||
frameCb->sendCopy(wlBuffer->resource());
|
||||
|
||||
Debug::log(TRACE, "[sc] wlr frame copied");
|
||||
});
|
||||
|
||||
frameCb->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
|
||||
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this);
|
||||
|
||||
w = width;
|
||||
h = height;
|
||||
fmt = format;
|
||||
|
||||
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
|
||||
});
|
||||
|
||||
frameCb->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) {
|
||||
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this);
|
||||
|
||||
if (!onBufferReady()) {
|
||||
Debug::log(ERR, "onBufferReady failed");
|
||||
return;
|
||||
}
|
||||
|
||||
frameCb.reset();
|
||||
});
|
||||
|
||||
frameCb->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||
Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)this);
|
||||
|
||||
frameSize = stride * height;
|
||||
frameStride = stride;
|
||||
});
|
||||
}
|
||||
|
||||
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, w, h, 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, 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, w, h, 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, w, h, 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);
|
||||
|
||||
auto params = makeShared<CCZwpLinuxBufferParamsV1>(g_pHyprlock->dma.linuxDmabuf->sendCreateParams());
|
||||
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");
|
||||
params.reset();
|
||||
gbm_bo_destroy(bo);
|
||||
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
|
||||
close(fd[plane_tmp]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
params->sendAdd(fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff);
|
||||
}
|
||||
|
||||
wlBuffer = makeShared<CCWlBuffer>(params->sendCreateImmed(w, h, fmt, (zwpLinuxBufferParamsV1Flags)0));
|
||||
params.reset();
|
||||
|
||||
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{w, 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++] = 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,43 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../core/Output.hpp"
|
||||
#include <gbm.h>
|
||||
#include "Shared.hpp"
|
||||
#include "linux-dmabuf-v1.hpp"
|
||||
#include "wlr-screencopy-unstable-v1.hpp"
|
||||
|
||||
class CDMAFrame;
|
||||
|
||||
class CDMAFrame {
|
||||
public:
|
||||
static std::string getResourceId(COutput* output);
|
||||
|
||||
CDMAFrame(COutput* mon);
|
||||
~CDMAFrame();
|
||||
|
||||
bool onBufferDone();
|
||||
bool onBufferReady();
|
||||
|
||||
SP<CCWlBuffer> 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];
|
||||
|
||||
SP<CCZwlrScreencopyFrameV1> frameCb = nullptr;
|
||||
int w = 0, h = 0;
|
||||
uint32_t fmt = 0;
|
||||
size_t frameSize = 0;
|
||||
size_t frameStride = 0;
|
||||
|
||||
EGLImage image = nullptr;
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#include "Framebuffer.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <utility>
|
||||
|
||||
static uint32_t drmFormatToGL(uint32_t drm) {
|
||||
switch (drm) {
|
||||
|
|
@ -97,7 +99,7 @@ void CFramebuffer::bind() const {
|
|||
glViewport(0, 0, m_vSize.x, m_vSize.y);
|
||||
}
|
||||
|
||||
void CFramebuffer::release() {
|
||||
void CFramebuffer::destroyBuffer() {
|
||||
if (m_iFb != (uint32_t)-1 && m_iFb)
|
||||
glDeleteFramebuffers(1, &m_iFb);
|
||||
|
||||
|
|
@ -114,9 +116,9 @@ void CFramebuffer::release() {
|
|||
}
|
||||
|
||||
CFramebuffer::~CFramebuffer() {
|
||||
release();
|
||||
destroyBuffer();
|
||||
}
|
||||
|
||||
bool CFramebuffer::isAllocated() {
|
||||
bool CFramebuffer::isAllocated() const {
|
||||
return m_iFb != (GLuint)-1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@ class CFramebuffer {
|
|||
public:
|
||||
~CFramebuffer();
|
||||
|
||||
bool alloc(int w, int h, bool highres = false);
|
||||
void addStencil();
|
||||
void bind() const;
|
||||
void release();
|
||||
void reset();
|
||||
bool isAllocated();
|
||||
bool alloc(int w, int h, bool highres = false);
|
||||
void addStencil();
|
||||
void bind() const;
|
||||
void destroyBuffer();
|
||||
bool isAllocated() const;
|
||||
|
||||
Vector2D m_vSize;
|
||||
Vector2D m_vSize;
|
||||
|
||||
CTexture m_cTex;
|
||||
GLuint m_iFb = -1;
|
||||
CTexture m_cTex;
|
||||
GLuint m_iFb = -1;
|
||||
|
||||
CTexture* m_pStencilTex = nullptr;
|
||||
};
|
||||
CTexture* m_pStencilTex = nullptr;
|
||||
|
||||
CFramebuffer& operator=(CFramebuffer&&) = delete;
|
||||
CFramebuffer& operator=(const CFramebuffer&) = delete;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +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 "../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 "src/helpers/Log.hpp"
|
||||
#include "widgets/PasswordInputField.hpp"
|
||||
#include "widgets/Background.hpp"
|
||||
#include "widgets/Label.hpp"
|
||||
|
|
@ -23,12 +25,12 @@ inline const float fullVerts[] = {
|
|||
0, 1, // bottom left
|
||||
};
|
||||
|
||||
GLuint compileShader(const GLuint& type, std::string src) {
|
||||
static GLuint compileShader(const GLuint& type, std::string src) {
|
||||
auto shader = glCreateShader(type);
|
||||
|
||||
auto shaderSource = src.c_str();
|
||||
|
||||
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
|
||||
glShaderSource(shader, 1, &shaderSource, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint ok;
|
||||
|
|
@ -39,14 +41,14 @@ GLuint compileShader(const GLuint& type, std::string src) {
|
|||
return shader;
|
||||
}
|
||||
|
||||
GLuint createProgram(const std::string& vert, const std::string& frag) {
|
||||
static GLuint createProgram(const std::string& vert, const std::string& frag) {
|
||||
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
|
||||
|
||||
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert.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);
|
||||
|
|
@ -76,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;
|
||||
|
|
@ -186,19 +188,17 @@ CRenderer::CRenderer() {
|
|||
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>();
|
||||
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
|
||||
}
|
||||
|
||||
static int frames = 0;
|
||||
static bool firstFullFrame = false;
|
||||
|
||||
//
|
||||
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
|
||||
static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar");
|
||||
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
|
||||
|
||||
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
|
||||
g_pEGL->makeCurrent(surf.eglSurface);
|
||||
|
|
@ -215,52 +215,18 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
|
|||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
SRenderFeedback feedback;
|
||||
float bga = 0.0;
|
||||
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
|
||||
|
||||
if (WAITFORASSETS) {
|
||||
|
||||
// render status
|
||||
if (!**PDISABLEBAR) {
|
||||
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
|
||||
renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!firstFullFrame) {
|
||||
firstFullFrameTime = std::chrono::system_clock::now();
|
||||
firstFullFrame = true;
|
||||
}
|
||||
|
||||
bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - firstFullFrameTime).count() / 500000.0, 0.0, 1.0);
|
||||
|
||||
if (g_pHyprlock->m_bNoFadeIn)
|
||||
bga = 1.0;
|
||||
|
||||
if (g_pHyprlock->m_bFadeStarted && !**PNOFADEOUT) {
|
||||
bga =
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(g_pHyprlock->m_tFadeEnds - std::chrono::system_clock::now()).count() / 500000.0 - 0.02, 0.0, 1.0);
|
||||
// - 0.02 so that the fade ends a little earlier than the final second
|
||||
}
|
||||
// render widgets
|
||||
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
|
||||
for (auto& w : *WIDGETS) {
|
||||
feedback.needsFrame = w->draw({bga}) || feedback.needsFrame;
|
||||
}
|
||||
// render widgets
|
||||
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->gathered || bga < 1.0;
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
return feedback;
|
||||
}
|
||||
|
||||
void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
|
||||
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);
|
||||
|
|
@ -298,12 +264,11 @@ void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient
|
|||
|
||||
glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
|
||||
|
||||
static_assert(sizeof(CColor) == 4 * sizeof(float)); // otherwise the line below this will fail
|
||||
|
||||
glUniform4fv(borderShader.gradient, gradient.m_vColors.size(), (float*)gradient.m_vColors.data());
|
||||
glUniform1i(borderShader.gradientLength, gradient.m_vColors.size());
|
||||
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);
|
||||
|
|
@ -414,62 +379,49 @@ void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTe
|
|||
glBindTexture(tex.m_iTarget, 0);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) {
|
||||
if (!widgets.contains(surf)) {
|
||||
template <class Widget>
|
||||
static void createWidget(std::vector<ASP<IWidget>>& widgets) {
|
||||
const auto W = makeAtomicShared<Widget>();
|
||||
W->registerSelf(W);
|
||||
widgets.emplace_back(W);
|
||||
}
|
||||
|
||||
std::vector<ASP<IWidget>>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface& surf) {
|
||||
RASSERT(surf.m_outputID != OUTPUT_INVALID, "Invalid output ID!");
|
||||
|
||||
if (!widgets.contains(surf.m_outputID)) {
|
||||
auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
|
||||
|
||||
std::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) &&
|
||||
!surf->output->stringDesc.starts_with("desc:" + 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->gathered) {
|
||||
if (!asyncResourceGatherer->getAssetByID(resourceID))
|
||||
resourceID = ""; // Fallback to solid color (background:color)
|
||||
}
|
||||
|
||||
if (!g_pHyprlock->getScreencopy()) {
|
||||
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
|
||||
resourceID = "";
|
||||
}
|
||||
|
||||
} else if (!PATH.empty())
|
||||
resourceID = "background:" + PATH;
|
||||
|
||||
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, surf->output, resourceID, c.values, PATH == "screenshot"));
|
||||
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) {
|
||||
|
|
@ -635,6 +587,31 @@ 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) {
|
||||
*opacity = 0.f;
|
||||
|
||||
if (unlock)
|
||||
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
|
||||
}
|
||||
|
||||
void CRenderer::warpOpacity(float newOpacity) {
|
||||
opacity->setValueAndWarp(newOpacity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include "Shader.hpp"
|
||||
#include "../defines.hpp"
|
||||
#include "../core/LockSurface.hpp"
|
||||
#include "../helpers/AnimatedVariable.hpp"
|
||||
#include "../helpers/Color.hpp"
|
||||
#include "AsyncResourceGatherer.hpp"
|
||||
#include "../config/ConfigDataValues.hpp"
|
||||
#include "widgets/IWidget.hpp"
|
||||
#include "Framebuffer.hpp"
|
||||
|
||||
typedef std::unordered_map<const CSessionLockSurface*, std::vector<std::unique_ptr<IWidget>>> widgetMap_t;
|
||||
typedef std::unordered_map<OUTPUTID, std::vector<ASP<IWidget>>> widgetMap_t;
|
||||
|
||||
class CRenderer {
|
||||
public:
|
||||
|
|
@ -22,46 +22,51 @@ 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;
|
||||
float boostA = 1.0;
|
||||
int size = 0, passes = 0;
|
||||
float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0;
|
||||
std::optional<CHyprColor> colorize;
|
||||
float boostA = 1.0;
|
||||
};
|
||||
|
||||
SRenderFeedback renderLock(const CSessionLockSurface& surface);
|
||||
SRenderFeedback renderLock(const CSessionLockSurface& surf);
|
||||
|
||||
void renderRect(const CBox& box, const CColor& col, int rounding = 0);
|
||||
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 firstFullFrameTime;
|
||||
std::chrono::system_clock::time_point firstFullFrameTime;
|
||||
|
||||
void pushFb(GLint fb);
|
||||
void popFb();
|
||||
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);
|
||||
void warpOpacity(float warpOpacity);
|
||||
std::vector<ASP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
|
||||
|
||||
private:
|
||||
widgetMap_t widgets;
|
||||
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;
|
||||
|
||||
CShader rectShader;
|
||||
CShader texShader;
|
||||
CShader texMixShader;
|
||||
CShader blurShader1;
|
||||
CShader blurShader2;
|
||||
CShader blurPrepareShader;
|
||||
CShader blurFinishShader;
|
||||
CShader borderShader;
|
||||
Mat3x3 projMatrix = Mat3x3::identity();
|
||||
Mat3x3 projection;
|
||||
|
||||
Mat3x3 projMatrix = Mat3x3::identity();
|
||||
Mat3x3 projection;
|
||||
PHLANIMVAR<float> opacity;
|
||||
|
||||
std::vector<GLint> boundFBs;
|
||||
std::vector<GLint> boundFBs;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CRenderer> g_pRenderer;
|
||||
inline UP<CRenderer> g_pRenderer;
|
||||
|
|
|
|||
477
src/renderer/Screencopy.cpp
Normal file
477
src/renderer/Screencopy.cpp
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
#include "Screencopy.hpp"
|
||||
#include "./AsyncResourceManager.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../core/Egl.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "wlr-screencopy-unstable-v1.hpp"
|
||||
#include <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;
|
||||
|
||||
//
|
||||
void CScreencopyFrame::capture(SP<COutput> pOutput) {
|
||||
RASSERT(pOutput, "Screencopy, but no valid output");
|
||||
|
||||
static const auto SCMODE = g_pConfigManager->getValue<Hyprlang::INT>("general:screencopy_mode");
|
||||
|
||||
m_asset = makeAtomicShared<CTexture>();
|
||||
m_resourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort);
|
||||
|
||||
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, pOutput->m_wlOutput->resource()));
|
||||
|
||||
if (*SCMODE == 1)
|
||||
m_frame = makeUnique<CSCSHMFrame>(m_sc);
|
||||
else
|
||||
m_frame = makeUnique<CSCDMAFrame>(m_sc);
|
||||
|
||||
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();
|
||||
m_ready = true;
|
||||
g_asyncResourceManager->screencopyToTexture(*this);
|
||||
});
|
||||
}
|
||||
|
||||
CSCDMAFrame::CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
|
||||
if (!glEGLImageTargetTexture2DOES) {
|
||||
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
if (!glEGLImageTargetTexture2DOES) {
|
||||
Debug::log(ERR, "[sc] No glEGLImageTargetTexture2DOES??");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_pHyprlock->dma.linuxDmabuf) {
|
||||
Debug::log(ERR, "[sc] No DMABUF support?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_pHyprlock->dma.gbmDevice) {
|
||||
Debug::log(ERR, "[sc] No gbmDevice for DMABUF was created?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eglQueryDmaBufModifiersEXT)
|
||||
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
|
||||
|
||||
m_sc->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
|
||||
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this);
|
||||
|
||||
m_w = width;
|
||||
m_h = height;
|
||||
m_fmt = format;
|
||||
|
||||
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
|
||||
});
|
||||
|
||||
m_sc->setBuffer([](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
|
||||
; // unused by dma
|
||||
});
|
||||
}
|
||||
|
||||
CSCDMAFrame::~CSCDMAFrame() {
|
||||
if (g_pEGL)
|
||||
eglDestroyImage(g_pEGL->eglDisplay, m_image);
|
||||
|
||||
// leaks bo and stuff but lives throughout so for now who cares
|
||||
}
|
||||
|
||||
bool CSCDMAFrame::onBufferDone() {
|
||||
uint32_t flags = GBM_BO_USE_RENDERING;
|
||||
|
||||
if (!eglQueryDmaBufModifiersEXT) {
|
||||
Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support");
|
||||
m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags);
|
||||
} else {
|
||||
std::array<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(ASP<CTexture> texture) {
|
||||
static constexpr struct {
|
||||
EGLAttrib fd;
|
||||
EGLAttrib offset;
|
||||
EGLAttrib pitch;
|
||||
EGLAttrib modlo;
|
||||
EGLAttrib modhi;
|
||||
} attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT,
|
||||
.offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT,
|
||||
.pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT,
|
||||
.modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
|
||||
.modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},
|
||||
{.fd = EGL_DMA_BUF_PLANE1_FD_EXT,
|
||||
.offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT,
|
||||
.pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT,
|
||||
.modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
|
||||
.modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},
|
||||
{.fd = EGL_DMA_BUF_PLANE2_FD_EXT,
|
||||
.offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT,
|
||||
.pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT,
|
||||
.modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
|
||||
.modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},
|
||||
{.fd = EGL_DMA_BUF_PLANE3_FD_EXT,
|
||||
.offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT,
|
||||
.pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT,
|
||||
.modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
|
||||
.modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}};
|
||||
|
||||
std::vector<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;
|
||||
}
|
||||
|
||||
texture->allocate();
|
||||
texture->m_vSize = {m_w, m_h};
|
||||
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
Debug::log(LOG, "Got dma frame with size {}", texture->m_vSize);
|
||||
|
||||
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(ASP<CTexture> texture) {
|
||||
convertBuffer();
|
||||
|
||||
texture->allocate();
|
||||
texture->m_vSize.x = m_w;
|
||||
texture->m_vSize.y = m_h;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
|
||||
|
||||
void* buffer = m_convBuffer ? m_convBuffer : m_shmData;
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", texture->m_vSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
92
src/renderer/Screencopy.hpp
Normal file
92
src/renderer/Screencopy.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../core/Output.hpp"
|
||||
#include "../renderer/Texture.hpp"
|
||||
#include <cstdint>
|
||||
#include <gbm.h>
|
||||
#include "linux-dmabuf-v1.hpp"
|
||||
#include "wlr-screencopy-unstable-v1.hpp"
|
||||
|
||||
class ISCFrame {
|
||||
public:
|
||||
ISCFrame() = default;
|
||||
virtual ~ISCFrame() = default;
|
||||
|
||||
virtual bool onBufferDone() = 0;
|
||||
virtual bool onBufferReady(ASP<CTexture> asset) = 0;
|
||||
|
||||
SP<CCWlBuffer> m_wlBuffer = nullptr;
|
||||
};
|
||||
|
||||
class CScreencopyFrame {
|
||||
public:
|
||||
CScreencopyFrame() = default;
|
||||
~CScreencopyFrame() = default;
|
||||
|
||||
void capture(SP<COutput> pOutput);
|
||||
|
||||
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
|
||||
|
||||
size_t m_resourceID;
|
||||
ASP<CTexture> m_asset;
|
||||
|
||||
bool m_ready = false;
|
||||
|
||||
private:
|
||||
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(ASP<CTexture> 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(ASP<CTexture> texture);
|
||||
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;
|
||||
};
|
||||
|
|
@ -41,9 +41,13 @@ class CShader {
|
|||
GLint applyTint = -1;
|
||||
GLint tint = -1;
|
||||
|
||||
GLint gradient = -1;
|
||||
GLint gradientLength = -1;
|
||||
GLint angle = -1;
|
||||
GLint gradient = -1;
|
||||
GLint gradientLength = -1;
|
||||
GLint gradient2 = -1;
|
||||
GLint gradient2Length = -1;
|
||||
GLint gradientLerp = -1;
|
||||
GLint angle = -1;
|
||||
GLint angle2 = -1;
|
||||
|
||||
GLint time = -1;
|
||||
GLint distort = -1;
|
||||
|
|
|
|||
|
|
@ -427,12 +427,32 @@ 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;
|
||||
|
||||
vec4 getColorForCoord(vec2 normalizedCoord) {
|
||||
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];
|
||||
|
||||
|
|
@ -461,6 +481,46 @@ vec4 getColorForCoord(vec2 normalizedCoord) {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "Texture.hpp"
|
||||
|
||||
CTexture::CTexture() {
|
||||
// naffin'
|
||||
; // naffin'
|
||||
}
|
||||
|
||||
CTexture::~CTexture() {
|
||||
|
|
@ -20,4 +20,4 @@ void CTexture::allocate() {
|
|||
if (!m_bAllocated)
|
||||
glGenTextures(1, &m_iTexID);
|
||||
m_bAllocated = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
src/renderer/resources/TextCmdResource.cpp
Normal file
31
src/renderer/resources/TextCmdResource.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#include "TextCmdResource.hpp"
|
||||
|
||||
#include "../../config/ConfigManager.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
|
||||
using namespace Hyprgraphics;
|
||||
|
||||
CTextCmdResource::CTextCmdResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) {
|
||||
;
|
||||
}
|
||||
|
||||
void CTextCmdResource::render() {
|
||||
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
|
||||
|
||||
CTextResource::STextResourceData textData = m_data;
|
||||
|
||||
textData.text = spawnSync(m_data.text);
|
||||
|
||||
if (*TRIM) {
|
||||
textData.text.erase(0, textData.text.find_first_not_of(" \n\r\t"));
|
||||
textData.text.erase(textData.text.find_last_not_of(" \n\r\t") + 1);
|
||||
}
|
||||
|
||||
Hyprgraphics::CTextResource textResource(std::move(textData));
|
||||
|
||||
textResource.render();
|
||||
|
||||
std::swap(m_asset, textResource.m_asset);
|
||||
}
|
||||
15
src/renderer/resources/TextCmdResource.hpp
Normal file
15
src/renderer/resources/TextCmdResource.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
|
||||
class CTextCmdResource : public Hyprgraphics::IAsyncResource {
|
||||
public:
|
||||
CTextCmdResource(Hyprgraphics::CTextResource::STextResourceData&& data);
|
||||
virtual ~CTextCmdResource() = default;
|
||||
|
||||
virtual void render();
|
||||
|
||||
private:
|
||||
Hyprgraphics::CTextResource::STextResourceData m_data;
|
||||
};
|
||||
|
|
@ -1,32 +1,33 @@
|
|||
#include "Background.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../AsyncResourceManager.hpp"
|
||||
#include "../Framebuffer.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
#include "../../core/AnimationManager.hpp"
|
||||
#include "../../config/ConfigManager.hpp"
|
||||
#include <chrono>
|
||||
#include <hyprlang.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <GLES3/gl32.h>
|
||||
|
||||
CBackground::~CBackground() {
|
||||
|
||||
if (reloadTimer) {
|
||||
reloadTimer->cancel();
|
||||
reloadTimer.reset();
|
||||
}
|
||||
|
||||
if (fade) {
|
||||
if (fade->crossFadeTimer) {
|
||||
fade->crossFadeTimer->cancel();
|
||||
fade->crossFadeTimer.reset();
|
||||
}
|
||||
fade.reset();
|
||||
}
|
||||
CBackground::CBackground() {
|
||||
blurredFB = makeUnique<CFramebuffer>();
|
||||
pendingBlurredFB = makeUnique<CFramebuffer>();
|
||||
transformedScFB = makeUnique<CFramebuffer>();
|
||||
}
|
||||
|
||||
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 ASP<CBackground>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
void CBackground::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
|
||||
reset();
|
||||
|
||||
try {
|
||||
color = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
|
|
@ -40,7 +41,6 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
|
|||
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()); //
|
||||
|
|
@ -48,119 +48,118 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
|
|||
RASSERT(false, "Missing propperty for CBackground: {}", e.what()); //
|
||||
}
|
||||
|
||||
if (!isScreenshot && reloadTime > -1) {
|
||||
isScreenshot = path == "screenshot";
|
||||
|
||||
viewport = pOutput->getViewport();
|
||||
outputPort = pOutput->stringPort;
|
||||
transform = wlTransformToHyprutils(invertTransform(pOutput->transform));
|
||||
scResourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort);
|
||||
|
||||
g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
|
||||
|
||||
if (!g_asyncResourceManager->checkIdPresent(scResourceID)) {
|
||||
Debug::log(LOG, "Missing screenshot for output {}", outputPort);
|
||||
scResourceID = 0;
|
||||
}
|
||||
|
||||
if (isScreenshot) {
|
||||
resourceID = scResourceID; // Fallback to solid background:color when scResourceID==0
|
||||
|
||||
if (!g_pHyprlock->getScreencopy()) {
|
||||
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
|
||||
resourceID = 0;
|
||||
}
|
||||
} else if (!path.empty())
|
||||
resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr);
|
||||
|
||||
if (!reloadCommand.empty() && reloadTime > -1) {
|
||||
try {
|
||||
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
|
||||
if (!isScreenshot)
|
||||
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
|
||||
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
|
||||
|
||||
plantReloadTimer(); // No reloads for screenshots.
|
||||
plantReloadTimer(); // No reloads if reloadCommand is empty
|
||||
}
|
||||
}
|
||||
|
||||
void CBackground::renderRect(CColor color) {
|
||||
void CBackground::reset() {
|
||||
if (reloadTimer) {
|
||||
reloadTimer->cancel();
|
||||
reloadTimer.reset();
|
||||
}
|
||||
|
||||
blurredFB->destroyBuffer();
|
||||
pendingBlurredFB->destroyBuffer();
|
||||
}
|
||||
|
||||
void CBackground::updatePrimaryAsset() {
|
||||
if (asset || resourceID == 0)
|
||||
return;
|
||||
|
||||
asset = g_asyncResourceManager->getAssetByID(resourceID);
|
||||
if (!asset)
|
||||
return;
|
||||
|
||||
const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender);
|
||||
if (NEEDFB)
|
||||
renderToFB(*asset, *blurredFB, blurPasses, isScreenshot);
|
||||
}
|
||||
|
||||
void CBackground::updatePendingAsset() {
|
||||
// For crossfading a new asset
|
||||
if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated())
|
||||
return;
|
||||
|
||||
renderToFB(*pendingAsset, *pendingBlurredFB, blurPasses);
|
||||
}
|
||||
|
||||
void CBackground::updateScAsset() {
|
||||
if (scAsset || scResourceID == 0)
|
||||
return;
|
||||
|
||||
// path=screenshot -> scAsset = asset
|
||||
scAsset = (asset && isScreenshot) ? asset : g_asyncResourceManager->getAssetByID(scResourceID);
|
||||
if (!scAsset)
|
||||
return;
|
||||
|
||||
const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL;
|
||||
if (NEEDSCTRANSFORM)
|
||||
renderToFB(*scAsset, *transformedScFB, 0, true);
|
||||
}
|
||||
|
||||
const CTexture& CBackground::getPrimaryAssetTex() const {
|
||||
// This case is only for background:path=screenshot with blurPasses=0
|
||||
if (isScreenshot && blurPasses == 0 && transformedScFB->isAllocated())
|
||||
return transformedScFB->m_cTex;
|
||||
|
||||
return (blurredFB->isAllocated()) ? blurredFB->m_cTex : *asset;
|
||||
}
|
||||
|
||||
const CTexture& CBackground::getPendingAssetTex() const {
|
||||
return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : *pendingAsset;
|
||||
}
|
||||
|
||||
const CTexture& CBackground::getScAssetTex() const {
|
||||
return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : *scAsset;
|
||||
}
|
||||
|
||||
void CBackground::renderRect(CHyprColor color) {
|
||||
CBox monbox = {0, 0, viewport.x, viewport.y};
|
||||
g_pRenderer->renderRect(monbox, color, 0);
|
||||
}
|
||||
|
||||
static void onReloadTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PBG = (CBackground*)data;
|
||||
|
||||
PBG->onReloadTimerUpdate();
|
||||
PBG->plantReloadTimer();
|
||||
static void onReloadTimer(AWP<CBackground> ref) {
|
||||
if (auto PBG = ref.lock(); PBG) {
|
||||
PBG->onReloadTimerUpdate();
|
||||
PBG->plantReloadTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onCrossFadeTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PBG = (CBackground*)data;
|
||||
PBG->onCrossFadeTimerUpdate();
|
||||
}
|
||||
static CBox getScaledBoxForTextureSize(const Vector2D& size, const Vector2D& viewport) {
|
||||
CBox texbox = {{}, size};
|
||||
|
||||
static void onAssetCallback(void* data) {
|
||||
const auto PBG = (CBackground*)data;
|
||||
PBG->startCrossFadeOrUpdateRender();
|
||||
}
|
||||
|
||||
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
onAssetCallback(data);
|
||||
}
|
||||
|
||||
bool CBackground::draw(const SRenderData& data) {
|
||||
|
||||
if (resourceID.empty()) {
|
||||
CColor col = color;
|
||||
col.a *= data.opacity;
|
||||
renderRect(col);
|
||||
return data.opacity < 1.0;
|
||||
}
|
||||
|
||||
if (!asset)
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
|
||||
if (!asset) {
|
||||
CColor col = color;
|
||||
col.a *= data.opacity;
|
||||
renderRect(col);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (asset->texture.m_iType == TEXTURE_INVALID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
resourceID = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (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) {
|
||||
size.x = asset->texture.m_vSize.y;
|
||||
size.y = asset->texture.m_vSize.x;
|
||||
}
|
||||
|
||||
CBox texbox = {{}, size};
|
||||
|
||||
float scaleX = viewport.x / size.x;
|
||||
float scaleY = viewport.y / size.y;
|
||||
|
||||
texbox.w *= std::max(scaleX, scaleY);
|
||||
texbox.h *= std::max(scaleX, scaleY);
|
||||
|
||||
if (scaleX > scaleY)
|
||||
texbox.y = -(texbox.h - viewport.y) / 2.f;
|
||||
else
|
||||
texbox.x = -(texbox.w - viewport.x) / 2.f;
|
||||
texbox.round();
|
||||
|
||||
if (!blurredFB.isAllocated())
|
||||
blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit
|
||||
|
||||
blurredFB.bind();
|
||||
|
||||
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,
|
||||
HYPRUTILS_TRANSFORM_NORMAL);
|
||||
else
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0,
|
||||
isScreenshot ?
|
||||
wlTransformToHyprutils(invertTransform(output->transform)) :
|
||||
HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs
|
||||
|
||||
if (blurPasses > 0)
|
||||
g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness});
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
CTexture* tex = blurredFB.isAllocated() ? &blurredFB.m_cTex : &asset->texture;
|
||||
|
||||
CBox texbox = {{}, tex->m_vSize};
|
||||
|
||||
Vector2D size = tex->m_vSize;
|
||||
float scaleX = viewport.x / tex->m_vSize.x;
|
||||
float scaleY = viewport.y / tex->m_vSize.y;
|
||||
float scaleX = viewport.x / size.x;
|
||||
float scaleY = viewport.y / size.y;
|
||||
|
||||
texbox.w *= std::max(scaleX, scaleY);
|
||||
texbox.h *= std::max(scaleX, scaleY);
|
||||
|
|
@ -170,38 +169,122 @@ 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, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
||||
|
||||
return fade || data.opacity < 1.0; // actively render during fading
|
||||
return texbox;
|
||||
}
|
||||
|
||||
void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, bool applyTransform) {
|
||||
if (firstRender)
|
||||
firstRender = false;
|
||||
|
||||
// make it brah
|
||||
Vector2D size = tex.m_vSize;
|
||||
if (applyTransform && transform % 2 == 1) {
|
||||
size.x = tex.m_vSize.y;
|
||||
size.y = tex.m_vSize.x;
|
||||
}
|
||||
|
||||
const auto TEXBOX = getScaledBoxForTextureSize(size, viewport);
|
||||
|
||||
if (!fb.isAllocated())
|
||||
fb.alloc(viewport.x, viewport.y); // TODO 10 bit
|
||||
|
||||
fb.bind();
|
||||
|
||||
g_pRenderer->renderTexture(TEXBOX, tex, 1.0, 0, applyTransform ? transform : HYPRUTILS_TRANSFORM_NORMAL);
|
||||
|
||||
if (blurPasses > 0)
|
||||
g_pRenderer->blurFB(fb,
|
||||
CRenderer::SBlurParams{
|
||||
.size = blurSize,
|
||||
.passes = passes,
|
||||
.noise = noise,
|
||||
.contrast = contrast,
|
||||
.brightness = brightness,
|
||||
.vibrancy = vibrancy,
|
||||
.vibrancy_darkness = vibrancy_darkness,
|
||||
});
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
bool CBackground::draw(const SRenderData& data) {
|
||||
updatePrimaryAsset();
|
||||
updatePendingAsset();
|
||||
updateScAsset();
|
||||
|
||||
if (asset && asset->m_iType == TEXTURE_INVALID) {
|
||||
g_asyncResourceManager->unload(asset);
|
||||
resourceID = 0;
|
||||
renderRect(color);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!asset || resourceID == 0) {
|
||||
// fade in/out with a solid color
|
||||
if (data.opacity < 1.0 && scAsset) {
|
||||
const auto& SCTEX = getScAssetTex();
|
||||
const auto SCTEXBOX = getScaledBoxForTextureSize(SCTEX.m_vSize, viewport);
|
||||
g_pRenderer->renderTexture(SCTEXBOX, SCTEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
||||
CHyprColor col = color;
|
||||
col.a *= data.opacity;
|
||||
renderRect(col);
|
||||
return true;
|
||||
}
|
||||
|
||||
renderRect(color);
|
||||
return !asset && resourceID > 0; // resource not ready
|
||||
}
|
||||
|
||||
const auto& TEX = getPrimaryAssetTex();
|
||||
const auto TEXBOX = getScaledBoxForTextureSize(TEX.m_vSize, viewport);
|
||||
if (data.opacity < 1.0 && scAsset) {
|
||||
const auto& SCTEX = getScAssetTex();
|
||||
g_pRenderer->renderTextureMix(TEXBOX, SCTEX, TEX, 1.0, data.opacity, 0);
|
||||
} else if (crossFadeProgress->isBeingAnimated()) {
|
||||
const auto& PENDINGTEX = getPendingAssetTex();
|
||||
g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0);
|
||||
} else
|
||||
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0);
|
||||
|
||||
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
|
||||
}
|
||||
|
||||
void CBackground::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
|
||||
pendingResource = false;
|
||||
|
||||
if (!newAsset)
|
||||
Debug::log(ERR, "Background asset update failed, resourceID: {} not available on update!", id);
|
||||
else if (newAsset->m_iType == TEXTURE_INVALID) {
|
||||
g_asyncResourceManager->unload(newAsset);
|
||||
Debug::log(ERR, "New background asset has an invalid texture!");
|
||||
} else {
|
||||
pendingAsset = newAsset;
|
||||
crossFadeProgress->setValueAndWarp(0);
|
||||
*crossFadeProgress = 1.0;
|
||||
|
||||
crossFadeProgress->setCallbackOnEnd(
|
||||
[REF = m_self, id](auto) {
|
||||
if (const auto PSELF = REF.lock()) {
|
||||
if (PSELF->asset)
|
||||
g_asyncResourceManager->unload(PSELF->asset);
|
||||
PSELF->asset = PSELF->pendingAsset;
|
||||
PSELF->pendingAsset = nullptr;
|
||||
PSELF->resourceID = id;
|
||||
|
||||
PSELF->blurredFB->destroyBuffer();
|
||||
PSELF->blurredFB = std::move(PSELF->pendingBlurredFB);
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void CBackground::plantReloadTimer() {
|
||||
|
||||
if (reloadTime == 0)
|
||||
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onReloadTimer, this, true);
|
||||
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), onReloadTimer, this, false);
|
||||
}
|
||||
|
||||
void CBackground::onCrossFadeTimerUpdate() {
|
||||
|
||||
// Animation done: Unload previous asset, deinitialize the fade and pass the asset
|
||||
|
||||
if (fade) {
|
||||
fade->crossFadeTimer.reset();
|
||||
fade.reset(nullptr);
|
||||
}
|
||||
|
||||
if (!(blurPasses > 0 || isScreenshot))
|
||||
blurredFB.release();
|
||||
|
||||
asset = pendingAsset;
|
||||
resourceID = pendingResourceID;
|
||||
pendingResourceID = "";
|
||||
pendingAsset = nullptr;
|
||||
firstRender = true;
|
||||
|
||||
g_pHyprlock->renderOutput(output->stringPort);
|
||||
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
|
||||
}
|
||||
|
||||
void CBackground::onReloadTimerUpdate() {
|
||||
|
|
@ -210,7 +293,7 @@ void CBackground::onReloadTimerUpdate() {
|
|||
// Path parsing and early returns
|
||||
|
||||
if (!reloadCommand.empty()) {
|
||||
path = g_pHyprlock->spawnSync(reloadCommand);
|
||||
path = spawnSync(reloadCommand);
|
||||
|
||||
if (path.ends_with('\n'))
|
||||
path.pop_back();
|
||||
|
|
@ -228,58 +311,22 @@ void CBackground::onReloadTimerUpdate() {
|
|||
return;
|
||||
|
||||
modificationTime = MTIME;
|
||||
if (OLDPATH == path)
|
||||
m_imageRevision++;
|
||||
else
|
||||
m_imageRevision = 0;
|
||||
} catch (std::exception& e) {
|
||||
path = OLDPATH;
|
||||
Debug::log(ERR, "{}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pendingResourceID.empty())
|
||||
if (pendingResource)
|
||||
return;
|
||||
|
||||
pendingResource = true;
|
||||
|
||||
// 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 = onAssetCallback;
|
||||
request.callbackData = this;
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
AWP<IWidget> widget(m_self);
|
||||
g_asyncResourceManager->requestImage(path, m_imageRevision, widget);
|
||||
}
|
||||
|
||||
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 = std::make_unique<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)), onCrossFadeTimer, this);
|
||||
} 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), onAssetCallbackTimer, this);
|
||||
}
|
||||
|
||||
g_pHyprlock->renderOutput(output->stringPort);
|
||||
}
|
||||
|
|
@ -1,70 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../defines.hpp"
|
||||
#include "../../helpers/AnimatedVariable.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();
|
||||
~CBackground();
|
||||
|
||||
virtual bool draw(const SRenderData& data);
|
||||
void renderRect(CColor color);
|
||||
void registerSelf(const ASP<CBackground>& self);
|
||||
|
||||
void onReloadTimerUpdate();
|
||||
void onCrossFadeTimerUpdate();
|
||||
void plantReloadTimer();
|
||||
void startCrossFadeOrUpdateRender();
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
|
||||
virtual bool draw(const SRenderData& data);
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
|
||||
|
||||
void reset(); // Unload assets, remove timers, etc.
|
||||
|
||||
void updatePrimaryAsset();
|
||||
void updatePendingAsset();
|
||||
void updateScAsset();
|
||||
|
||||
const CTexture& getPrimaryAssetTex() const;
|
||||
const CTexture& getPendingAssetTex() const;
|
||||
const CTexture& getScAssetTex() const;
|
||||
|
||||
void renderRect(CHyprColor color);
|
||||
void renderToFB(const CTexture& text, CFramebuffer& fb, int passes, bool applyTransform = false);
|
||||
|
||||
void onReloadTimerUpdate();
|
||||
void plantReloadTimer();
|
||||
void startCrossFade();
|
||||
|
||||
private:
|
||||
AWP<CBackground> m_self;
|
||||
|
||||
// if needed
|
||||
CFramebuffer blurredFB;
|
||||
UP<CFramebuffer> blurredFB;
|
||||
UP<CFramebuffer> pendingBlurredFB;
|
||||
UP<CFramebuffer> transformedScFB;
|
||||
|
||||
int blurSize = 10;
|
||||
int blurPasses = 3;
|
||||
float noise = 0.0117;
|
||||
float contrast = 0.8916;
|
||||
float brightness = 0.8172;
|
||||
float vibrancy = 0.1696;
|
||||
float vibrancy_darkness = 0.0;
|
||||
Vector2D viewport;
|
||||
std::string path = "";
|
||||
int blurSize = 10;
|
||||
int blurPasses = 3;
|
||||
float noise = 0.0117;
|
||||
float contrast = 0.8916;
|
||||
float brightness = 0.8172;
|
||||
float vibrancy = 0.1696;
|
||||
float vibrancy_darkness = 0.0;
|
||||
Vector2D viewport;
|
||||
std::string path = "";
|
||||
|
||||
std::string resourceID;
|
||||
std::string pendingResourceID;
|
||||
std::string outputPort;
|
||||
Hyprutils::Math::eTransform transform;
|
||||
|
||||
float crossFadeTime = -1.0;
|
||||
ResourceID resourceID = 0;
|
||||
ResourceID scResourceID = 0;
|
||||
bool pendingResource = false;
|
||||
|
||||
CColor color;
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
COutput* output = nullptr;
|
||||
bool isScreenshot = false;
|
||||
SPreloadedAsset* pendingAsset = nullptr;
|
||||
bool firstRender = true;
|
||||
PHLANIMVAR<float> crossFadeProgress;
|
||||
|
||||
std::unique_ptr<SFade> fade;
|
||||
CHyprColor color;
|
||||
ASP<CTexture> asset = nullptr;
|
||||
ASP<CTexture> scAsset = nullptr;
|
||||
ASP<CTexture> pendingAsset = nullptr;
|
||||
bool isScreenshot = false;
|
||||
bool firstRender = true;
|
||||
|
||||
int reloadTime = -1;
|
||||
std::string reloadCommand;
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
std::shared_ptr<CTimer> reloadTimer;
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
};
|
||||
int reloadTime = -1;
|
||||
std::string reloadCommand;
|
||||
ASP<CTimer> reloadTimer;
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
size_t m_imageRevision = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
#include "../../helpers/Log.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../auth/Auth.hpp"
|
||||
#include "../../auth/Fingerprint.hpp"
|
||||
#include <chrono>
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
|
|
@ -11,20 +11,22 @@
|
|||
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 190100
|
||||
#if defined(_LIBCPP_VERSION)
|
||||
#pragma comment(lib, "date-tz")
|
||||
#include <date/tz.h>
|
||||
namespace std {
|
||||
namespace chrono {
|
||||
using date::current_zone;
|
||||
using date::locate_zone;
|
||||
using date::time_zone;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Vector2D rotateVector(const Vector2D& vec, const double& ang) {
|
||||
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);
|
||||
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) {
|
||||
|
|
@ -75,9 +77,19 @@ int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int
|
|||
return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER);
|
||||
}
|
||||
|
||||
Hyprgraphics::CTextResource::eTextAlignmentMode IWidget::parseTextAlignment(const std::string& alignment) {
|
||||
Hyprgraphics::CTextResource::eTextAlignmentMode align = Hyprgraphics::CTextResource::TEXT_ALIGN_LEFT;
|
||||
if (alignment == "center")
|
||||
align = Hyprgraphics::CTextResource::TEXT_ALIGN_CENTER;
|
||||
else if (alignment == "right")
|
||||
align = Hyprgraphics::CTextResource::TEXT_ALIGN_RIGHT;
|
||||
|
||||
return align;
|
||||
}
|
||||
|
||||
static void replaceAllAttempts(std::string& str) {
|
||||
|
||||
const size_t ATTEMPTS = g_pAuth->m_iFailedAttempts;
|
||||
const size_t ATTEMPTS = g_pAuth->getFailedAttempts();
|
||||
const std::string STR = std::to_string(ATTEMPTS);
|
||||
size_t pos = 0;
|
||||
|
||||
|
|
@ -99,64 +111,74 @@ 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 LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
|
||||
const auto LAYOUTNAME = xkb_keymap_layout_get_name(g_pSeatManager->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);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string LANG = LANGS[LAYOUTIDX].empty() ? layoutName : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
|
||||
str.replace(pos, 9 + REPL.length(), LANG);
|
||||
pos += LANG.length();
|
||||
} else {
|
||||
str.replace(pos, 7, STR);
|
||||
pos += STR.length();
|
||||
str.replace(pos, 7, layoutName);
|
||||
pos += layoutName.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool logMissingTzOnce = true;
|
||||
static std::string getTime() {
|
||||
const auto PCURRENTTZ = std::chrono::current_zone();
|
||||
const auto TPNOW = 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 (!pCurrentTz) {
|
||||
if (logMissingTzOnce) {
|
||||
Debug::log(WARN, "Current timezone unknown for $TIME. Falling back to UTC!");
|
||||
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))};
|
||||
hhmmss = std::chrono::hh_mm_ss{pCurrentTz->to_local(TPNOW) - std::chrono::floor<std::chrono::days>(pCurrentTz->to_local(TPNOW))};
|
||||
|
||||
const auto HRS = hhmmss.hours().count();
|
||||
const auto MINS = hhmmss.minutes().count();
|
||||
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 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 for $TIME12. 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))};
|
||||
|
||||
const auto HRS = hhmmss.hours().count();
|
||||
const auto MINS = hhmmss.minutes().count();
|
||||
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");
|
||||
}
|
||||
|
|
@ -164,8 +186,8 @@ static std::string getTime12h() {
|
|||
IWidget::SFormatResult IWidget::formatString(std::string in) {
|
||||
|
||||
auto uidPassword = getpwuid(getuid());
|
||||
char* username = uidPassword->pw_name;
|
||||
char* user_gecos = uidPassword->pw_gecos;
|
||||
char* username = uidPassword ? uidPassword->pw_name : nullptr;
|
||||
char* user_gecos = uidPassword ? uidPassword->pw_gecos : nullptr;
|
||||
|
||||
if (!username)
|
||||
Debug::log(ERR, "Error in formatString, username null. Errno: ", errno);
|
||||
|
|
@ -184,22 +206,10 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
|
|||
}
|
||||
|
||||
if (in.contains("$TIME")) {
|
||||
replaceInString(in, "$TIME", getTime());
|
||||
replaceInString(in, "$TIME", getTime24h());
|
||||
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
|
||||
}
|
||||
|
||||
if (in.contains("$FAIL")) {
|
||||
const auto FAIL = g_pAuth->getFailText(AUTH_IMPL_PAM);
|
||||
replaceInString(in, "$FAIL", FAIL.value_or(""));
|
||||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.contains("$PROMPT")) {
|
||||
const auto PROMPT = g_pAuth->getPrompt(AUTH_IMPL_PAM);
|
||||
replaceInString(in, "$PROMPT", PROMPT.value_or(""));
|
||||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.contains("$ATTEMPTS")) {
|
||||
replaceAllAttempts(in);
|
||||
result.allowForceUpdate = true;
|
||||
|
|
@ -210,9 +220,27 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
|
|||
result.allowForceUpdate = true;
|
||||
}
|
||||
|
||||
if (in.contains("$FPRINTMESSAGE")) {
|
||||
const auto FPRINTMESSAGE = g_pAuth->getFailText(AUTH_IMPL_FINGERPRINT);
|
||||
replaceInString(in, "$FPRINTMESSAGE", FPRINTMESSAGE.value_or(""));
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -249,3 +277,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,21 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../defines.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Seat.hpp"
|
||||
#include "../Texture.hpp"
|
||||
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <any>
|
||||
|
||||
class COutput;
|
||||
|
||||
class IWidget {
|
||||
public:
|
||||
struct SRenderData {
|
||||
float opacity = 1;
|
||||
};
|
||||
|
||||
virtual ~IWidget() = default;
|
||||
|
||||
virtual bool draw(const SRenderData& data) = 0;
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
|
||||
virtual bool draw(const SRenderData& data) = 0;
|
||||
// Never render within onAssetUpdate!
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) = 0;
|
||||
|
||||
static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
|
||||
const double& ang = 0);
|
||||
static int roundingForBox(const CBox& box, int roundingConfig);
|
||||
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
|
||||
static Hyprgraphics::CTextResource::eTextAlignmentMode parseTextAlignment(const std::string& alignment);
|
||||
|
||||
virtual CBox getBoundingBoxWl() const {
|
||||
return CBox();
|
||||
};
|
||||
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}
|
||||
virtual void onHover(const Vector2D& pos) {}
|
||||
bool containsPoint(const Vector2D& pos) const;
|
||||
|
||||
struct SFormatResult {
|
||||
std::string formatted;
|
||||
|
|
@ -26,4 +47,10 @@ class IWidget {
|
|||
};
|
||||
|
||||
static SFormatResult formatString(std::string in);
|
||||
|
||||
void setHover(bool hover);
|
||||
bool isHovered() const;
|
||||
|
||||
private:
|
||||
bool hovered = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,40 +1,39 @@
|
|||
#include "Image.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../AsyncResourceManager.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include <cmath>
|
||||
#include <hyprlang.hpp>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
|
||||
CImage::~CImage() {
|
||||
if (imageTimer) {
|
||||
imageTimer->cancel();
|
||||
imageTimer.reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
void CImage::registerSelf(const ASP<CImage>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
static void onTimer(AWP<CImage> ref) {
|
||||
if (auto PIMAGE = ref.lock(); PIMAGE) {
|
||||
PIMAGE->onTimerUpdate();
|
||||
PIMAGE->plantTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
const auto PIMAGE = (CImage*)data;
|
||||
|
||||
PIMAGE->onTimerUpdate();
|
||||
PIMAGE->plantTimer();
|
||||
}
|
||||
|
||||
static void onAssetCallback(void* data) {
|
||||
const auto PIMAGE = (CImage*)data;
|
||||
PIMAGE->renderUpdate();
|
||||
}
|
||||
|
||||
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
onAssetCallback(data);
|
||||
}
|
||||
|
||||
void CImage::onTimerUpdate() {
|
||||
if (m_pendingResource) {
|
||||
Debug::log(WARN, "Trying to update image, but a resource is still pending! Skipping update.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string OLDPATH = path;
|
||||
|
||||
if (!reloadCommand.empty()) {
|
||||
path = g_pHyprlock->spawnSync(reloadCommand);
|
||||
path = spawnSync(reloadCommand);
|
||||
|
||||
if (path.ends_with('\n'))
|
||||
path.pop_back();
|
||||
|
|
@ -52,57 +51,60 @@ void CImage::onTimerUpdate() {
|
|||
return;
|
||||
|
||||
modificationTime = MTIME;
|
||||
if (OLDPATH == path)
|
||||
m_imageRevision++;
|
||||
else
|
||||
m_imageRevision = 0;
|
||||
} catch (std::exception& e) {
|
||||
path = OLDPATH;
|
||||
Debug::log(ERR, "{}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pendingResourceID.empty())
|
||||
return;
|
||||
m_pendingResource = true;
|
||||
|
||||
request.id = std::string{"image:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count());
|
||||
pendingResourceID = request.id;
|
||||
request.asset = path;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
|
||||
|
||||
request.callback = onAssetCallback;
|
||||
request.callbackData = this;
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
AWP<IWidget> widget(m_self);
|
||||
g_asyncResourceManager->requestImage(path, m_imageRevision, widget);
|
||||
}
|
||||
|
||||
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, props, viewport);
|
||||
|
||||
try {
|
||||
size = std::any_cast<Hyprlang::INT>(props.at("size"));
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
|
||||
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
|
||||
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"));
|
||||
size = std::any_cast<Hyprlang::INT>(props.at("size"));
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
|
||||
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
|
||||
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
|
||||
|
||||
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
|
||||
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
|
||||
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
|
||||
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()); //
|
||||
}
|
||||
|
||||
angle = angle * M_PI / 180.0;
|
||||
resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr);
|
||||
angle = angle * M_PI / 180.0;
|
||||
|
||||
if (reloadTime > -1) {
|
||||
try {
|
||||
|
|
@ -113,20 +115,39 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r
|
|||
}
|
||||
}
|
||||
|
||||
void CImage::reset() {
|
||||
if (imageTimer) {
|
||||
imageTimer->cancel();
|
||||
imageTimer.reset();
|
||||
}
|
||||
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
return;
|
||||
|
||||
imageFB.destroyBuffer();
|
||||
|
||||
if (asset && reloadTime > -1) // Don't unload asset if it's a static image
|
||||
g_asyncResourceManager->unload(asset);
|
||||
|
||||
asset = nullptr;
|
||||
m_pendingResource = false;
|
||||
resourceID = 0;
|
||||
}
|
||||
|
||||
bool CImage::draw(const SRenderData& data) {
|
||||
|
||||
if (resourceID.empty())
|
||||
if (resourceID == 0)
|
||||
return false;
|
||||
|
||||
if (!asset)
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
asset = g_asyncResourceManager->getAssetByID(resourceID);
|
||||
|
||||
if (!asset)
|
||||
return true;
|
||||
|
||||
if (asset->texture.m_iType == TEXTURE_INVALID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
resourceID = "";
|
||||
if (asset->m_iType == TEXTURE_INVALID) {
|
||||
g_asyncResourceManager->unload(asset);
|
||||
resourceID = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +155,7 @@ bool CImage::draw(const SRenderData& data) {
|
|||
|
||||
const Vector2D IMAGEPOS = {border, border};
|
||||
const Vector2D BORDERPOS = {0.0, 0.0};
|
||||
const Vector2D TEXSIZE = asset->texture.m_vSize;
|
||||
const Vector2D TEXSIZE = asset->m_vSize;
|
||||
const float SCALEX = size / TEXSIZE.x;
|
||||
const float SCALEY = size / TEXSIZE.y;
|
||||
|
||||
|
|
@ -162,7 +183,7 @@ bool CImage::draw(const SRenderData& data) {
|
|||
g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0);
|
||||
|
||||
texbox.round();
|
||||
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
g_pRenderer->renderTexture(texbox, *asset, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
g_pRenderer->popFb();
|
||||
}
|
||||
|
||||
|
|
@ -176,10 +197,10 @@ 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;
|
||||
|
|
@ -188,25 +209,40 @@ bool CImage::draw(const SRenderData& data) {
|
|||
return data.opacity < 1.0;
|
||||
}
|
||||
|
||||
void CImage::renderUpdate() {
|
||||
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
|
||||
if (newAsset) {
|
||||
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
|
||||
} else if (resourceID != pendingResourceID) {
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
imageFB.release();
|
||||
void CImage::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
|
||||
m_pendingResource = false;
|
||||
|
||||
asset = newAsset;
|
||||
resourceID = pendingResourceID;
|
||||
firstRender = true;
|
||||
}
|
||||
pendingResourceID = "";
|
||||
} else if (!pendingResourceID.empty()) {
|
||||
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
|
||||
if (!newAsset)
|
||||
Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id);
|
||||
else if (newAsset->m_iType == TEXTURE_INVALID) {
|
||||
g_asyncResourceManager->unload(newAsset);
|
||||
Debug::log(ERR, "New image asset has an invalid texture!");
|
||||
} else {
|
||||
g_asyncResourceManager->unload(asset);
|
||||
imageFB.destroyBuffer();
|
||||
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
|
||||
asset = newAsset;
|
||||
resourceID = id;
|
||||
firstRender = true;
|
||||
}
|
||||
|
||||
g_pHyprlock->renderOutput(output->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,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../defines.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "../AsyncResourceGatherer.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
|
@ -17,39 +17,57 @@ 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();
|
||||
|
||||
void registerSelf(const ASP<CImage>& self);
|
||||
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
|
||||
virtual bool draw(const SRenderData& data);
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
|
||||
|
||||
virtual CBox getBoundingBoxWl() const;
|
||||
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
|
||||
virtual void onHover(const Vector2D& pos);
|
||||
|
||||
void reset();
|
||||
|
||||
void renderUpdate();
|
||||
void onTimerUpdate();
|
||||
void plantTimer();
|
||||
|
||||
private:
|
||||
CFramebuffer imageFB;
|
||||
AWP<CImage> m_self;
|
||||
|
||||
int size;
|
||||
int rounding;
|
||||
double border;
|
||||
double angle;
|
||||
CGradientValueData color;
|
||||
Vector2D pos;
|
||||
CFramebuffer imageFB;
|
||||
|
||||
std::string halign, valign, path;
|
||||
int size = 0;
|
||||
int rounding = 0;
|
||||
double border = 0;
|
||||
double angle = 0;
|
||||
CGradientValueData color;
|
||||
Vector2D pos;
|
||||
Vector2D configPos;
|
||||
|
||||
bool firstRender = true;
|
||||
std::string halign, valign, path;
|
||||
|
||||
int reloadTime;
|
||||
std::string reloadCommand;
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
std::shared_ptr<CTimer> imageTimer;
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
bool firstRender = true;
|
||||
|
||||
Vector2D viewport;
|
||||
std::string resourceID;
|
||||
std::string pendingResourceID; // if reloading image
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
COutput* output = nullptr;
|
||||
CShadowable shadow;
|
||||
int reloadTime;
|
||||
std::string reloadCommand;
|
||||
std::string onclickCommand;
|
||||
|
||||
std::filesystem::file_time_type modificationTime;
|
||||
size_t m_imageRevision = 0;
|
||||
|
||||
ASP<CTimer> imageTimer;
|
||||
|
||||
Vector2D viewport;
|
||||
std::string stringPort;
|
||||
|
||||
ResourceID resourceID = 0;
|
||||
bool m_pendingResource = false;
|
||||
|
||||
ASP<CTexture> asset = nullptr;
|
||||
CShadowable shadow;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,45 +1,38 @@
|
|||
#include "Label.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../AsyncResourceManager.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "src/defines.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
CLabel::~CLabel() {
|
||||
if (labelTimer) {
|
||||
labelTimer->cancel();
|
||||
labelTimer.reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
void CLabel::registerSelf(const ASP<CLabel>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
static void onTimer(AWP<CLabel> ref) {
|
||||
if (auto PLABEL = ref.lock(); PLABEL) {
|
||||
// update label
|
||||
PLABEL->onTimerUpdate();
|
||||
// plant new timer
|
||||
PLABEL->plantTimer();
|
||||
}
|
||||
}
|
||||
|
||||
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
if (data == nullptr)
|
||||
return;
|
||||
const auto PLABEL = (CLabel*)data;
|
||||
|
||||
// update label
|
||||
PLABEL->onTimerUpdate();
|
||||
|
||||
// plant new timer
|
||||
PLABEL->plantTimer();
|
||||
}
|
||||
|
||||
static void onAssetCallback(void* data) {
|
||||
const auto PLABEL = (CLabel*)data;
|
||||
PLABEL->renderUpdate();
|
||||
}
|
||||
|
||||
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
|
||||
onAssetCallback(data);
|
||||
}
|
||||
|
||||
std::string CLabel::getUniqueResourceId() {
|
||||
return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
}
|
||||
|
||||
void CLabel::onTimerUpdate() {
|
||||
if (m_pendingResource) {
|
||||
Debug::log(WARN, "Trying to update label, but a resource is still pending! Skipping update.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string oldFormatted = label.formatted;
|
||||
|
||||
label = formatString(labelPreFormat);
|
||||
|
|
@ -47,57 +40,58 @@ void CLabel::onTimerUpdate() {
|
|||
if (label.formatted == oldFormatted && !label.alwaysUpdate)
|
||||
return;
|
||||
|
||||
if (!pendingResourceID.empty()) {
|
||||
Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID);
|
||||
return;
|
||||
}
|
||||
|
||||
// request new
|
||||
request.id = getUniqueResourceId();
|
||||
pendingResourceID = request.id;
|
||||
request.asset = label.formatted;
|
||||
request.text = label.formatted;
|
||||
m_pendingResource = true;
|
||||
|
||||
request.callback = onAssetCallback;
|
||||
request.callbackData = this;
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
AWP<IWidget> widget(m_self);
|
||||
if (label.cmd) {
|
||||
// Don't increment by one to avoid clashes with multiple widget using the same label command.
|
||||
m_dynamicRevision += label.updateEveryMs;
|
||||
g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock());
|
||||
} else
|
||||
g_asyncResourceManager->requestText(request, widget.lock());
|
||||
}
|
||||
|
||||
void CLabel::plantTimer() {
|
||||
|
||||
if (label.updateEveryMs != 0)
|
||||
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), 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, props, viewport);
|
||||
|
||||
try {
|
||||
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
|
||||
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);
|
||||
|
||||
request.id = getUniqueResourceId();
|
||||
resourceID = request.id;
|
||||
request.asset = label.formatted;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = fontFamily;
|
||||
request.props["color"] = labelColor;
|
||||
request.props["font_size"] = fontSize;
|
||||
request.props["cmd"] = label.cmd;
|
||||
request.text = label.formatted;
|
||||
request.font = fontFamily;
|
||||
request.fontSize = fontSize;
|
||||
request.color = labelColor.asRGB();
|
||||
|
||||
if (!textAlign.empty())
|
||||
request.props["text_align"] = textAlign;
|
||||
request.align = parseTextAlignment(textAlign);
|
||||
|
||||
} catch (const std::bad_any_cast& e) {
|
||||
RASSERT(false, "Failed to construct CLabel: {}", e.what()); //
|
||||
|
|
@ -105,17 +99,36 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string,
|
|||
RASSERT(false, "Missing property for CLabel: {}", e.what()); //
|
||||
}
|
||||
|
||||
configPos = pos;
|
||||
viewport = viewport_;
|
||||
pos = configPos; // Label size not known yet
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
if (label.cmd) {
|
||||
resourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, nullptr);
|
||||
} else
|
||||
resourceID = g_asyncResourceManager->requestText(request, nullptr);
|
||||
|
||||
plantTimer();
|
||||
}
|
||||
|
||||
void CLabel::reset() {
|
||||
if (labelTimer) {
|
||||
labelTimer->cancel();
|
||||
labelTimer.reset();
|
||||
}
|
||||
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
return;
|
||||
|
||||
if (asset)
|
||||
g_asyncResourceManager->unload(asset);
|
||||
|
||||
asset = nullptr;
|
||||
m_pendingResource = false;
|
||||
resourceID = 0;
|
||||
}
|
||||
|
||||
bool CLabel::draw(const SRenderData& data) {
|
||||
if (!asset) {
|
||||
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
||||
asset = g_asyncResourceManager->getAssetByID(resourceID);
|
||||
|
||||
if (!asset)
|
||||
return true;
|
||||
|
|
@ -129,30 +142,49 @@ bool CLabel::draw(const SRenderData& data) {
|
|||
shadow.draw(data);
|
||||
|
||||
// calc pos
|
||||
pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle);
|
||||
pos = posFromHVAlign(viewport, asset->m_vSize, configPos, halign, valign, angle);
|
||||
|
||||
CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y};
|
||||
CBox box = {pos.x, pos.y, asset->m_vSize.x, asset->m_vSize.y};
|
||||
box.rot = angle;
|
||||
g_pRenderer->renderTexture(box, asset->texture, data.opacity);
|
||||
g_pRenderer->renderTexture(box, *asset, data.opacity);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CLabel::renderUpdate() {
|
||||
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
|
||||
if (newAsset) {
|
||||
// new asset is ready :D
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
||||
asset = newAsset;
|
||||
resourceID = pendingResourceID;
|
||||
pendingResourceID = "";
|
||||
updateShadow = true;
|
||||
void CLabel::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
|
||||
Debug::log(TRACE, "Label update for resourceID {}", id);
|
||||
m_pendingResource = false;
|
||||
|
||||
if (!newAsset)
|
||||
Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id);
|
||||
else if (newAsset->m_iType == TEXTURE_INVALID) {
|
||||
g_asyncResourceManager->unload(newAsset);
|
||||
Debug::log(ERR, "New image asset has an invalid texture!");
|
||||
} else {
|
||||
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
|
||||
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
|
||||
return;
|
||||
// new asset is ready :D
|
||||
g_asyncResourceManager->unload(asset);
|
||||
asset = newAsset;
|
||||
resourceID = id;
|
||||
updateShadow = true;
|
||||
}
|
||||
|
||||
g_pHyprlock->renderOutput(outputStringPort);
|
||||
}
|
||||
|
||||
CBox CLabel::getBoundingBoxWl() const {
|
||||
if (!asset)
|
||||
return CBox{};
|
||||
|
||||
return {
|
||||
Vector2D{pos.x, viewport.y - pos.y - asset->m_vSize.y},
|
||||
asset->m_vSize,
|
||||
};
|
||||
}
|
||||
|
||||
void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) {
|
||||
if (down && !onclickCommand.empty())
|
||||
spawnAsync(onclickCommand);
|
||||
}
|
||||
|
||||
void CLabel::onHover(const Vector2D& pos) {
|
||||
if (!onclickCommand.empty())
|
||||
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../defines.hpp"
|
||||
#include "IWidget.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "../AsyncResourceGatherer.hpp"
|
||||
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <any>
|
||||
|
|
@ -14,36 +15,52 @@ 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();
|
||||
|
||||
void registerSelf(const ASP<CLabel>& self);
|
||||
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
|
||||
virtual bool draw(const SRenderData& data);
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
|
||||
|
||||
virtual CBox getBoundingBoxWl() const;
|
||||
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
|
||||
virtual void onHover(const Vector2D& pos);
|
||||
|
||||
void reset();
|
||||
|
||||
void renderUpdate();
|
||||
void onTimerUpdate();
|
||||
void plantTimer();
|
||||
|
||||
private:
|
||||
std::string getUniqueResourceId();
|
||||
AWP<CLabel> m_self;
|
||||
|
||||
std::string labelPreFormat;
|
||||
IWidget::SFormatResult label;
|
||||
std::string labelPreFormat;
|
||||
IWidget::SFormatResult label;
|
||||
|
||||
Vector2D viewport;
|
||||
Vector2D pos;
|
||||
Vector2D configPos;
|
||||
double angle;
|
||||
std::string resourceID;
|
||||
std::string pendingResourceID; // if dynamic label
|
||||
std::string halign, valign;
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
std::string halign, valign;
|
||||
std::string onclickCommand;
|
||||
|
||||
std::string outputStringPort;
|
||||
Vector2D viewport;
|
||||
Vector2D pos;
|
||||
Vector2D configPos;
|
||||
double angle;
|
||||
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
ResourceID resourceID = 0;
|
||||
bool m_pendingResource = false;
|
||||
|
||||
std::shared_ptr<CTimer> labelTimer = nullptr;
|
||||
size_t m_dynamicRevision = 0;
|
||||
|
||||
CShadowable shadow;
|
||||
bool updateShadow = true;
|
||||
ASP<CTexture> asset = nullptr;
|
||||
|
||||
std::string outputStringPort;
|
||||
|
||||
Hyprgraphics::CTextResource::STextResourceData request;
|
||||
|
||||
ASP<CTimer> labelTimer = nullptr;
|
||||
|
||||
CShadowable shadow;
|
||||
bool updateShadow = true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,41 @@
|
|||
#include "PasswordInputField.hpp"
|
||||
#include "../AsyncResourceManager.hpp"
|
||||
#include "../Renderer.hpp"
|
||||
#include "../../core/hyprlock.hpp"
|
||||
#include "../../auth/Auth.hpp"
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "../../config/ConfigManager.hpp"
|
||||
#include "../../helpers/Log.hpp"
|
||||
#include "../../core/AnimationManager.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include <cmath>
|
||||
#include <hyprgraphics/resource/resources/TextResource.hpp>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <algorithm>
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
|
||||
viewport(viewport_), outputStringPort(output), shadow(this, props, viewport_) {
|
||||
CPasswordInputField::~CPasswordInputField() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void CPasswordInputField::registerSelf(const ASP<CPasswordInputField>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
void CPasswordInputField::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
|
||||
reset();
|
||||
|
||||
outputStringPort = pOutput->stringPort;
|
||||
viewport = pOutput->getViewport();
|
||||
|
||||
shadow.configure(m_self, props, viewport);
|
||||
|
||||
try {
|
||||
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
|
||||
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_);
|
||||
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"));
|
||||
|
|
@ -22,7 +43,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
|
|||
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.fadeMs = std::any_cast<Hyprlang::INT>(props.at("dots_fade_time"));
|
||||
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"));
|
||||
|
|
@ -30,9 +50,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
|
|||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
configPlaceholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
|
||||
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
|
||||
configFailTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fail_timeout"));
|
||||
fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
|
||||
colorConfig.transitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition"));
|
||||
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"));
|
||||
|
|
@ -43,47 +61,65 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
|
|||
colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color"));
|
||||
colorConfig.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
|
||||
colorConfig.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
|
||||
colorConfig.hiddenBase = std::any_cast<Hyprlang::INT>(props.at("hide_input_base_color"));
|
||||
} catch (const std::bad_any_cast& e) {
|
||||
RASSERT(false, "Failed to construct CPasswordInputField: {}", e.what()); //
|
||||
} catch (const std::out_of_range& e) {
|
||||
RASSERT(false, "Missing property for CPasswordInputField: {}", e.what()); //
|
||||
}
|
||||
|
||||
configPos = pos;
|
||||
configSize = size;
|
||||
configPos = pos;
|
||||
colorState.font = colorConfig.font;
|
||||
|
||||
pos = posFromHVAlign(viewport, size, pos, halign, valign);
|
||||
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
|
||||
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
|
||||
colorConfig.transitionMs = std::clamp(colorConfig.transitionMs, 0, 1000);
|
||||
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
|
||||
pos = posFromHVAlign(viewport, configSize, pos, halign, valign);
|
||||
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
|
||||
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
|
||||
|
||||
colorState.inner = colorConfig.inner;
|
||||
colorState.outer = *colorConfig.outer;
|
||||
colorState.font = colorConfig.font;
|
||||
colorState.outerSource = colorConfig.outer;
|
||||
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
|
||||
|
||||
g_pAnimationManager->createAnimation(0.f, fade.a, g_pConfigManager->m_AnimationTree.getConfig("inputFieldFade"));
|
||||
g_pAnimationManager->createAnimation(0.f, dots.currentAmount, g_pConfigManager->m_AnimationTree.getConfig("inputFieldDots"));
|
||||
g_pAnimationManager->createAnimation(configSize, size, g_pConfigManager->m_AnimationTree.getConfig("inputFieldWidth"));
|
||||
g_pAnimationManager->createAnimation(colorConfig.inner, colorState.inner, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
|
||||
g_pAnimationManager->createAnimation(*colorConfig.outer, colorState.outer, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
|
||||
|
||||
srand(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign);
|
||||
|
||||
if (!dots.textFormat.empty()) {
|
||||
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
request.id = dots.textResourceID;
|
||||
request.asset = dots.textFormat;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = fontFamily;
|
||||
request.props["color"] = colorState.font;
|
||||
request.props["font_size"] = (int)(std::nearbyint(size.y * dots.size * 0.5f) * 2.f);
|
||||
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
Hyprgraphics::CTextResource::STextResourceData request;
|
||||
request.text = dots.textFormat;
|
||||
request.font = fontFamily;
|
||||
request.color = colorConfig.font.asRGB();
|
||||
request.fontSize = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
|
||||
dots.textResourceID = g_asyncResourceManager->requestText(request, nullptr);
|
||||
}
|
||||
|
||||
// request the inital placeholder asset
|
||||
updatePlaceholder();
|
||||
}
|
||||
|
||||
static void fadeOutCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
CPasswordInputField* p = (CPasswordInputField*)data;
|
||||
void CPasswordInputField::reset() {
|
||||
if (fade.fadeOutTimer) {
|
||||
fade.fadeOutTimer->cancel();
|
||||
fade.fadeOutTimer.reset();
|
||||
}
|
||||
|
||||
p->onFadeOutTimer();
|
||||
if (g_pHyprlock->m_bTerminate)
|
||||
return;
|
||||
|
||||
if (placeholder.asset)
|
||||
g_asyncResourceManager->unload(placeholder.asset);
|
||||
|
||||
placeholder.asset = nullptr;
|
||||
placeholder.resourceID = 0;
|
||||
placeholder.currentText.clear();
|
||||
}
|
||||
|
||||
static void fadeOutCallback(AWP<CPasswordInputField> ref) {
|
||||
if (const auto PP = ref.lock(); PP)
|
||||
PP->onFadeOutTimer();
|
||||
}
|
||||
|
||||
void CPasswordInputField::onFadeOutTimer() {
|
||||
|
|
@ -95,7 +131,7 @@ void CPasswordInputField::onFadeOutTimer() {
|
|||
|
||||
void CPasswordInputField::updateFade() {
|
||||
if (!fadeOnEmpty) {
|
||||
fade.a = 1.0;
|
||||
fade.a->setValueAndWarp(1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -109,73 +145,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);
|
||||
}
|
||||
} else if (!fade.fadeOutTimer)
|
||||
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;
|
||||
|
||||
// Fully fading the dots to 0 currently does not look good
|
||||
if (passwordLength == 0 && dots.currentAmount > 2) {
|
||||
dots.currentAmount = 0;
|
||||
if (checkWaiting)
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::abs(passwordLength - dots.currentAmount) > 1) {
|
||||
dots.currentAmount = std::clamp(dots.currentAmount, passwordLength - 1.f, passwordLength + 1.f);
|
||||
dots.lastFrame = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000);
|
||||
|
||||
const float TOADD = dots.fadeMs > 0 ? ((double)DELTA / 1000000.0) * (1000.0 / (double)dots.fadeMs) : 1;
|
||||
|
||||
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;
|
||||
|
|
@ -195,26 +192,29 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
updateWidth();
|
||||
updateHiddenInputState();
|
||||
|
||||
SRenderData shadowData = data;
|
||||
shadowData.opacity *= fade.a;
|
||||
CBox inputFieldBox = {pos, size->value()};
|
||||
CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}};
|
||||
|
||||
if (!dynamicWidth.animated || size.x > dynamicWidth.source)
|
||||
SRenderData shadowData = data;
|
||||
shadowData.opacity *= fade.a->value();
|
||||
|
||||
if (!size->isBeingAnimated())
|
||||
shadow.draw(shadowData);
|
||||
|
||||
CGradientValueData outerGrad = colorState.outer;
|
||||
for (auto& c : outerGrad.m_vColors)
|
||||
c.a *= fade.a * data.opacity;
|
||||
//CGradientValueData outerGrad = colorState.outer->value();
|
||||
//for (auto& c : outerGrad.m_vColors)
|
||||
// c.a *= fade.a->value() * data.opacity;
|
||||
|
||||
CColor innerCol = colorState.inner;
|
||||
innerCol.a *= fade.a * data.opacity;
|
||||
CColor fontCol = colorState.font;
|
||||
fontCol.a *= fade.a * data.opacity;
|
||||
CHyprColor innerCol = colorState.inner->value();
|
||||
innerCol.a *= fade.a->value() * data.opacity;
|
||||
CHyprColor fontCol = colorState.font;
|
||||
fontCol.a *= fade.a->value() * data.opacity;
|
||||
|
||||
if (outThick > 0) {
|
||||
const int BORDERROUND = roundingForBorderBox(outerBox, rounding, outThick);
|
||||
g_pRenderer->renderBorder(outerBox, outerGrad, outThick, BORDERROUND, fade.a * data.opacity);
|
||||
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);
|
||||
|
|
@ -224,7 +224,7 @@ 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->renderBorder(outerBox, hiddenInputState.lastColor, outThick, BORDERROUND, fade.a * data.opacity);
|
||||
g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity);
|
||||
glScissor(0, 0, viewport.x, viewport.y);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
|
@ -233,177 +233,160 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
|||
const int ROUND = roundingForBox(inputFieldBox, rounding);
|
||||
g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND);
|
||||
|
||||
if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) {
|
||||
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);
|
||||
dots.textAsset = g_asyncResourceManager->getAssetByID(dots.textResourceID);
|
||||
|
||||
if (!dots.textAsset)
|
||||
forceReload = true;
|
||||
else {
|
||||
passSize = dots.textAsset->texture.m_vSize;
|
||||
passSize = dots.textAsset->m_vSize;
|
||||
passSpacing = std::floor(passSize.x * dots.spacing);
|
||||
}
|
||||
}
|
||||
|
||||
const int DOT_PAD = (inputFieldBox.h - passSize.y) / 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 / (passSize.x + passSpacing)); // max amount of dots that can fit in the area
|
||||
const int DOT_FLOORED = std::floor(dots.currentAmount);
|
||||
const float DOT_ALPHA = fontCol.a;
|
||||
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 = (passSize.x + passSpacing) * dots.currentAmount - passSpacing;
|
||||
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 * (passSize.x + passSpacing) - passSpacing - 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 = passSize.x / 2.0;
|
||||
else if (dots.rounding == -2)
|
||||
dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size;
|
||||
|
||||
for (int i = 0; i < 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 * (passSize.x + passSpacing), inputFieldBox.h / 2.f - passSize.y / 2.f};
|
||||
CBox box{dotPosition, passSize};
|
||||
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)
|
||||
if (!dots.textAsset) {
|
||||
forceReload = true;
|
||||
fontCol.a = DOTALPHA;
|
||||
break;
|
||||
}
|
||||
|
||||
g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding);
|
||||
} else {
|
||||
g_pRenderer->renderTexture(box, *dots.textAsset, 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()) {
|
||||
SPreloadedAsset* currAsset = nullptr;
|
||||
if (passwordLength == 0 && !checkWaiting && placeholder.resourceID > 0) {
|
||||
ASP<CTexture> currAsset = nullptr;
|
||||
|
||||
if (!placeholder.asset)
|
||||
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
|
||||
placeholder.asset = g_asyncResourceManager->getAssetByID(placeholder.resourceID);
|
||||
|
||||
currAsset = placeholder.asset;
|
||||
|
||||
if (currAsset && currAsset->texture.m_vSize.x + size.y <= size.x) {
|
||||
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);
|
||||
if (currAsset) {
|
||||
const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->m_vSize / 2.0;
|
||||
const CBox ASSETBOX{ASSETPOS, currAsset->m_vSize};
|
||||
|
||||
// Cut the texture to the width of the input field
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h);
|
||||
g_pRenderer->renderTexture(ASSETBOX, *currAsset, data.opacity * fade.a->value(), 0);
|
||||
glScissor(0, 0, viewport.x, viewport.y);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
} else
|
||||
forceReload = true;
|
||||
}
|
||||
|
||||
return dots.currentAmount != passwordLength || fade.animated || colorState.animated || redrawShadow || data.opacity < 1.0 || dynamicWidth.animated || forceReload;
|
||||
}
|
||||
|
||||
static void failTimeoutCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pAuth->m_bDisplayFailText = false;
|
||||
g_pHyprlock->renderAllOutputs();
|
||||
return redrawShadow || forceReload;
|
||||
}
|
||||
|
||||
void CPasswordInputField::updatePlaceholder() {
|
||||
if (passwordLength != 0) {
|
||||
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) {
|
||||
std::erase(placeholder.registeredResourceIDs, placeholder.resourceID);
|
||||
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
|
||||
g_asyncResourceManager->unload(placeholder.asset);
|
||||
placeholder.asset = nullptr;
|
||||
placeholder.resourceID = "";
|
||||
placeholder.resourceID = 0;
|
||||
redrawShadow = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto AUTHFEEDBACK = g_pAuth->getInlineFeedback();
|
||||
// already requested a placeholder for the current fail
|
||||
if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts())
|
||||
return;
|
||||
|
||||
placeholder.failedAttempts = g_pAuth->getFailedAttempts();
|
||||
|
||||
std::string newText = (displayFail) ? formatString(configFailText).formatted : formatString(configPlaceholderText).formatted;
|
||||
|
||||
// if the text is unchanged we don't need to do anything, unless we are swapping font color
|
||||
const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont;
|
||||
|
||||
if (!ALLOWCOLORSWAP && placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pAuth->m_iFailedAttempts == placeholder.failedAttempts)
|
||||
if (!ALLOWCOLORSWAP && newText == placeholder.currentText)
|
||||
return;
|
||||
|
||||
placeholder.failedAttempts = g_pAuth->m_iFailedAttempts;
|
||||
placeholder.lastAuthFeedback = AUTHFEEDBACK;
|
||||
|
||||
placeholder.asset = nullptr;
|
||||
|
||||
if (displayFail) {
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(configFailTimeoutMs), failTimeoutCallback, nullptr);
|
||||
const auto FORMATTEDFAIL = formatString(configFailText).formatted;
|
||||
placeholder.currentText = FORMATTEDFAIL;
|
||||
} else {
|
||||
const auto FORMATTEDPLACEHOLDER = formatString(configPlaceholderText).formatted;
|
||||
placeholder.currentText = FORMATTEDPLACEHOLDER;
|
||||
}
|
||||
|
||||
placeholder.resourceID =
|
||||
std::format("placeholder:{}{}{}{}{}{}", placeholder.currentText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a);
|
||||
|
||||
if (std::find(placeholder.registeredResourceIDs.begin(), placeholder.registeredResourceIDs.end(), placeholder.resourceID) != placeholder.registeredResourceIDs.end())
|
||||
return;
|
||||
|
||||
placeholder.registeredResourceIDs.push_back(placeholder.resourceID);
|
||||
Debug::log(LOG, "Updating placeholder text: {}", newText);
|
||||
placeholder.currentText = newText;
|
||||
placeholder.asset = nullptr;
|
||||
|
||||
// query
|
||||
CAsyncResourceGatherer::SPreloadRequest request;
|
||||
request.id = placeholder.resourceID;
|
||||
request.asset = placeholder.currentText;
|
||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||
request.props["font_family"] = fontFamily;
|
||||
request.props["color"] = colorState.font;
|
||||
request.props["font_size"] = (int)size.y / 4;
|
||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||
Hyprgraphics::CTextResource::STextResourceData request;
|
||||
request.text = placeholder.currentText;
|
||||
request.font = fontFamily;
|
||||
request.color = colorState.font.asRGB();
|
||||
request.fontSize = (int)size->value().y / 4;
|
||||
|
||||
AWP<IWidget> widget(m_self);
|
||||
placeholder.resourceID = g_asyncResourceManager->requestText(request, widget);
|
||||
}
|
||||
|
||||
void CPasswordInputField::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
|
||||
;
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateWidth() {
|
||||
const auto NOW = std::chrono::system_clock::now();
|
||||
double targetSizeX = configSize.x;
|
||||
double targetSizeX = configSize.x;
|
||||
|
||||
if (placeholder.asset)
|
||||
targetSizeX = placeholder.asset->texture.m_vSize.x + size.y;
|
||||
if (passwordLength == 0 && placeholder.asset)
|
||||
targetSizeX = placeholder.asset->m_vSize.x + size->goal().y;
|
||||
|
||||
if (targetSizeX < configSize.x)
|
||||
targetSizeX = configSize.x;
|
||||
targetSizeX = std::max(targetSizeX, configSize.x);
|
||||
|
||||
if (size.x != targetSizeX) {
|
||||
if (!dynamicWidth.animated) {
|
||||
dynamicWidth.source = size.x;
|
||||
dynamicWidth.start = NOW;
|
||||
dynamicWidth.animated = true;
|
||||
}
|
||||
|
||||
const auto TIMEDELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(NOW - dynamicWidth.start).count(), 1000, 100000);
|
||||
const auto INCR = std::clamp(std::abs(targetSizeX - dynamicWidth.source) * TIMEDELTA / 1000000.0, 1.0, 1000.0);
|
||||
if (size.x > targetSizeX)
|
||||
size.x -= INCR;
|
||||
else
|
||||
size.x += INCR;
|
||||
|
||||
if ((dynamicWidth.source < targetSizeX && size.x > targetSizeX) || (dynamicWidth.source > targetSizeX && size.x < targetSizeX)) {
|
||||
size.x = targetSizeX;
|
||||
redrawShadow = true;
|
||||
dynamicWidth.animated = false;
|
||||
}
|
||||
if (size->goal().x != targetSizeX) {
|
||||
*size = Vector2D{targetSizeX, configSize.y};
|
||||
size->setCallbackOnEnd([this](auto) {
|
||||
redrawShadow = true;
|
||||
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
|
||||
});
|
||||
}
|
||||
|
||||
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
|
||||
if (size->isBeingAnimated()) {
|
||||
redrawShadow = true;
|
||||
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
|
||||
}
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateHiddenInputState() {
|
||||
|
|
@ -413,78 +396,29 @@ 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);
|
||||
}
|
||||
|
||||
static void changeGrad(CGradientValueData* psource, CGradientValueData* ptarget, CGradientValueData& subject, const double& multi, bool& animated) {
|
||||
if (!psource || !ptarget)
|
||||
return;
|
||||
|
||||
subject.m_vColors.resize(ptarget->m_vColors.size(), subject.m_vColors.back());
|
||||
|
||||
for (size_t i = 0; i < subject.m_vColors.size(); ++i) {
|
||||
const CColor& sourceCol = (i < psource->m_vColors.size()) ? psource->m_vColors[i] : psource->m_vColors.back();
|
||||
const CColor& targetCol = (i < ptarget->m_vColors.size()) ? ptarget->m_vColors[i] : ptarget->m_vColors.back();
|
||||
changeColor(sourceCol, targetCol, subject.m_vColors[i], multi, animated);
|
||||
}
|
||||
|
||||
if (psource->m_fAngle != ptarget->m_fAngle) {
|
||||
const float DELTA = ptarget->m_fAngle - psource->m_fAngle;
|
||||
subject.m_fAngle += DELTA * multi;
|
||||
animated = true;
|
||||
|
||||
if ((psource->m_fAngle < ptarget->m_fAngle && subject.m_fAngle > ptarget->m_fAngle) || (psource->m_fAngle > ptarget->m_fAngle && subject.m_fAngle < ptarget->m_fAngle))
|
||||
subject.m_fAngle = ptarget->m_fAngle;
|
||||
}
|
||||
}
|
||||
|
||||
void CPasswordInputField::updateColors() {
|
||||
const bool BORDERLESS = outThick == 0;
|
||||
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
|
||||
const auto MULTI = colorConfig.transitionMs == 0 ?
|
||||
1.0 :
|
||||
std::clamp(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - colorState.lastFrame).count() / (double)colorConfig.transitionMs,
|
||||
0.0016, 0.5);
|
||||
const bool BORDERLESS = outThick == 0;
|
||||
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
|
||||
|
||||
//
|
||||
CGradientValueData* targetGrad = nullptr;
|
||||
|
||||
if (g_pHyprlock->m_bCapsLock && NUMLOCK && !colorConfig.both->m_bIsFallback)
|
||||
|
|
@ -496,41 +430,41 @@ void CPasswordInputField::updateColors() {
|
|||
|
||||
if (checkWaiting)
|
||||
targetGrad = colorConfig.check;
|
||||
else if (displayFail)
|
||||
else if (displayFail && passwordLength == 0)
|
||||
targetGrad = colorConfig.fail;
|
||||
|
||||
CGradientValueData* outerTarget = colorConfig.outer;
|
||||
CColor innerTarget = colorConfig.inner;
|
||||
CColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
|
||||
CHyprColor innerTarget = colorConfig.inner;
|
||||
CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
|
||||
|
||||
if (checkWaiting || displayFail || g_pHyprlock->m_bCapsLock || NUMLOCK) {
|
||||
if (targetGrad) {
|
||||
if (BORDERLESS && colorConfig.swapFont) {
|
||||
fontTarget = colorConfig.fail->m_vColors.front();
|
||||
fontTarget = targetGrad->m_vColors.front();
|
||||
} else if (BORDERLESS && !colorConfig.swapFont) {
|
||||
innerTarget = colorConfig.fail->m_vColors.front();
|
||||
// When changing the inner color the font cannot be fail_color
|
||||
innerTarget = targetGrad->m_vColors.front();
|
||||
// When changing the inner color, the font cannot be fail_color
|
||||
fontTarget = colorConfig.font;
|
||||
} else {
|
||||
} else if (targetGrad) {
|
||||
outerTarget = targetGrad;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetGrad != colorState.currentTarget) {
|
||||
colorState.outerSource = &colorState.outer;
|
||||
colorState.innerSource = colorState.inner;
|
||||
if (!BORDERLESS && *outerTarget != colorState.outer->goal())
|
||||
*colorState.outer = *outerTarget;
|
||||
|
||||
colorState.currentTarget = targetGrad;
|
||||
}
|
||||
if (innerTarget != colorState.inner->goal())
|
||||
*colorState.inner = innerTarget;
|
||||
|
||||
colorState.animated = false;
|
||||
|
||||
if (!BORDERLESS)
|
||||
changeGrad(colorState.outerSource, outerTarget, colorState.outer, MULTI, colorState.animated);
|
||||
changeColor(colorState.innerSource, innerTarget, colorState.inner, MULTI, colorState.animated);
|
||||
|
||||
// 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.
|
||||
colorState.font = fontTarget;
|
||||
|
||||
colorState.lastFrame = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
CBox CPasswordInputField::getBoundingBoxWl() const {
|
||||
return {
|
||||
Vector2D{pos.x, viewport.y - pos.y - size->value().y},
|
||||
size->value(),
|
||||
};
|
||||
}
|
||||
|
||||
void CPasswordInputField::onHover(const Vector2D& pos) {
|
||||
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "IWidget.hpp"
|
||||
#include "../../defines.hpp"
|
||||
#include "../../helpers/Color.hpp"
|
||||
#include "../../helpers/Math.hpp"
|
||||
#include "../../core/Timer.hpp"
|
||||
#include "Shadowable.hpp"
|
||||
#include "src/config/ConfigDataValues.hpp"
|
||||
#include <chrono>
|
||||
#include "../../config/ConfigDataValues.hpp"
|
||||
#include "../../helpers/AnimatedVariable.hpp"
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <vector>
|
||||
#include <any>
|
||||
#include <unordered_map>
|
||||
|
|
@ -15,116 +17,106 @@ 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 ASP<CPasswordInputField>& self);
|
||||
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
|
||||
virtual bool draw(const SRenderData& data);
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
|
||||
|
||||
virtual void onHover(const Vector2D& pos);
|
||||
virtual CBox getBoundingBoxWl() const;
|
||||
|
||||
void reset();
|
||||
void onFadeOutTimer();
|
||||
|
||||
private:
|
||||
void updateDots();
|
||||
void updateFade();
|
||||
void updatePlaceholder();
|
||||
void updateWidth();
|
||||
void updateHiddenInputState();
|
||||
void updateInputState();
|
||||
void updateColors();
|
||||
AWP<CPasswordInputField> m_self;
|
||||
|
||||
bool firstRender = true;
|
||||
bool redrawShadow = false;
|
||||
bool checkWaiting = false;
|
||||
bool displayFail = false;
|
||||
void updateDots();
|
||||
void updateFade();
|
||||
void updatePlaceholder();
|
||||
void updateWidth();
|
||||
void updateHiddenInputState();
|
||||
void updateInputState();
|
||||
void updateColors();
|
||||
|
||||
size_t passwordLength = 0;
|
||||
bool firstRender = true;
|
||||
bool redrawShadow = false;
|
||||
bool checkWaiting = false;
|
||||
bool displayFail = false;
|
||||
|
||||
Vector2D size;
|
||||
Vector2D pos;
|
||||
Vector2D viewport;
|
||||
Vector2D configPos;
|
||||
Vector2D configSize;
|
||||
size_t passwordLength = 0;
|
||||
|
||||
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
|
||||
uint64_t configFailTimeoutMs = 2000;
|
||||
PHLANIMVAR<Vector2D> size;
|
||||
Vector2D pos;
|
||||
Vector2D viewport;
|
||||
Vector2D configPos;
|
||||
Vector2D configSize;
|
||||
|
||||
int outThick, rounding;
|
||||
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
|
||||
uint64_t configFailTimeoutMs = 2000;
|
||||
|
||||
int outThick, rounding;
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point start;
|
||||
bool animated = false;
|
||||
double source = 0;
|
||||
} dynamicWidth;
|
||||
|
||||
struct {
|
||||
float currentAmount = 0;
|
||||
int fadeMs = 0;
|
||||
std::chrono::system_clock::time_point lastFrame;
|
||||
bool center = false;
|
||||
float size = 0;
|
||||
float spacing = 0;
|
||||
int rounding = 0;
|
||||
std::string textFormat = "";
|
||||
SPreloadedAsset* textAsset = nullptr;
|
||||
std::string textResourceID;
|
||||
PHLANIMVAR<float> currentAmount;
|
||||
bool center = false;
|
||||
float size = 0;
|
||||
float spacing = 0;
|
||||
int rounding = 0;
|
||||
size_t textResourceID = 0;
|
||||
std::string textFormat = "";
|
||||
ASP<CTexture> textAsset = nullptr;
|
||||
} dots;
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point start;
|
||||
float a = 0;
|
||||
bool appearing = true;
|
||||
bool animated = false;
|
||||
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
|
||||
bool allowFadeOut = false;
|
||||
PHLANIMVAR<float> a;
|
||||
bool appearing = true;
|
||||
ASP<CTimer> fadeOutTimer = nullptr;
|
||||
bool allowFadeOut = false;
|
||||
} fade;
|
||||
|
||||
struct {
|
||||
std::string resourceID = "";
|
||||
SPreloadedAsset* asset = nullptr;
|
||||
|
||||
std::string currentText = "";
|
||||
size_t failedAttempts = 0;
|
||||
bool canGetNewText = true;
|
||||
|
||||
std::string lastAuthFeedback;
|
||||
|
||||
std::vector<std::string> registeredResourceIDs;
|
||||
size_t resourceID = 0;
|
||||
ASP<CTexture> asset = nullptr;
|
||||
|
||||
std::string currentText = "";
|
||||
size_t failedAttempts = 0;
|
||||
} placeholder;
|
||||
|
||||
struct {
|
||||
CColor lastColor;
|
||||
int lastQuadrant = 0;
|
||||
int lastPasswordLength = 0;
|
||||
bool enabled = false;
|
||||
CHyprColor lastColor;
|
||||
int lastQuadrant = 0;
|
||||
int lastPasswordLength = 0;
|
||||
bool enabled = false;
|
||||
} hiddenInputState;
|
||||
|
||||
struct {
|
||||
CGradientValueData* outer = nullptr;
|
||||
CColor inner;
|
||||
CColor font;
|
||||
CHyprColor inner;
|
||||
CHyprColor font;
|
||||
CGradientValueData* fail = nullptr;
|
||||
CGradientValueData* check = nullptr;
|
||||
CGradientValueData* caps = nullptr;
|
||||
CGradientValueData* num = nullptr;
|
||||
CGradientValueData* both = nullptr;
|
||||
|
||||
CHyprColor hiddenBase;
|
||||
|
||||
int transitionMs = 0;
|
||||
bool invertNum = false;
|
||||
bool swapFont = false;
|
||||
} colorConfig;
|
||||
|
||||
struct {
|
||||
CGradientValueData outer;
|
||||
CColor inner;
|
||||
CColor font;
|
||||
|
||||
CGradientValueData* outerSource = nullptr;
|
||||
CColor innerSource;
|
||||
|
||||
CGradientValueData* currentTarget = nullptr;
|
||||
|
||||
bool animated = false;
|
||||
|
||||
//
|
||||
std::chrono::system_clock::time_point lastFrame;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
#include "../Renderer.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) : widget(widget_), viewport(viewport_) {
|
||||
void CShadowable::configure(AWP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) {
|
||||
m_widget = widget_;
|
||||
viewport = viewport_;
|
||||
|
||||
size = std::any_cast<Hyprlang::INT>(props.at("shadow_size"));
|
||||
passes = std::any_cast<Hyprlang::INT>(props.at("shadow_passes"));
|
||||
color = std::any_cast<Hyprlang::INT>(props.at("shadow_color"));
|
||||
|
|
@ -10,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;
|
||||
|
|
@ -22,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});
|
||||
|
|
@ -31,7 +38,7 @@ void CShadowable::markShadowDirty() {
|
|||
}
|
||||
|
||||
bool CShadowable::draw(const IWidget::SRenderData& data) {
|
||||
if (passes == 0)
|
||||
if (!m_widget || passes == 0)
|
||||
return true;
|
||||
|
||||
if (!shadowFB.isAllocated() || ignoreDraw)
|
||||
|
|
@ -40,4 +47,4 @@ bool CShadowable::draw(const IWidget::SRenderData& data) {
|
|||
CBox box = {0, 0, viewport.x, viewport.y};
|
||||
g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,22 +11,24 @@
|
|||
|
||||
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(AWP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
|
||||
|
||||
// instantly re-renders the shadow using the widget's draw() method
|
||||
void markShadowDirty();
|
||||
virtual bool draw(const IWidget::SRenderData& data);
|
||||
|
||||
private:
|
||||
IWidget* widget = nullptr;
|
||||
int size = 10;
|
||||
int passes = 4;
|
||||
float boostA = 1.0;
|
||||
CColor color{0, 0, 0, 1.0};
|
||||
Vector2D viewport;
|
||||
AWP<IWidget> m_widget;
|
||||
int size = 10;
|
||||
int passes = 4;
|
||||
float boostA = 1.0;
|
||||
CHyprColor color{0, 0, 0, 1.0};
|
||||
Vector2D viewport;
|
||||
|
||||
// to avoid recursive shadows
|
||||
bool ignoreDraw = false;
|
||||
|
||||
CFramebuffer shadowFB;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,30 +1,40 @@
|
|||
#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 ASP<CShape>& self) {
|
||||
m_self = self;
|
||||
}
|
||||
|
||||
void CShape::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
|
||||
viewport = pOutput->getViewport();
|
||||
|
||||
shadow.configure(m_self, props, viewport);
|
||||
|
||||
try {
|
||||
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_);
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
|
||||
color = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
|
||||
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
|
||||
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
|
||||
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
|
||||
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
|
||||
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
|
||||
color = std::any_cast<Hyprlang::INT>(props.at("color"));
|
||||
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
|
||||
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
|
||||
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
|
||||
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
|
||||
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
|
||||
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
|
||||
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
|
||||
} catch (const std::bad_any_cast& e) {
|
||||
RASSERT(false, "Failed to construct CShape: {}", e.what()); //
|
||||
} catch (const std::out_of_range& e) {
|
||||
RASSERT(false, "Missing property for CShape: {}", e.what()); //
|
||||
}
|
||||
|
||||
viewport = viewport_;
|
||||
angle = angle * M_PI / 180.0;
|
||||
angle = angle * M_PI / 180.0;
|
||||
|
||||
const Vector2D VBORDER = {border, border};
|
||||
const Vector2D REALSIZE = size + VBORDER * 2.0;
|
||||
|
|
@ -72,7 +82,7 @@ bool CShape::draw(const SRenderData& data) {
|
|||
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);
|
||||
|
|
@ -94,3 +104,24 @@ bool CShape::draw(const SRenderData& data) {
|
|||
|
||||
return data.opacity < 1.0;
|
||||
}
|
||||
|
||||
void CShape::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
|
||||
;
|
||||
}
|
||||
|
||||
CBox CShape::getBoundingBoxWl() const {
|
||||
return {
|
||||
Vector2D{pos.x, viewport.y - pos.y - size.y},
|
||||
size,
|
||||
};
|
||||
}
|
||||
|
||||
void CShape::onClick(uint32_t button, bool down, const Vector2D& pos) {
|
||||
if (down && !onclickCommand.empty())
|
||||
spawnAsync(onclickCommand);
|
||||
}
|
||||
|
||||
void CShape::onHover(const Vector2D& pos) {
|
||||
if (!onclickCommand.empty())
|
||||
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,17 +11,28 @@
|
|||
|
||||
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 ASP<CShape>& self);
|
||||
|
||||
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
|
||||
virtual bool draw(const SRenderData& data);
|
||||
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
|
||||
|
||||
virtual CBox getBoundingBoxWl() const;
|
||||
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
|
||||
virtual void onHover(const Vector2D& pos);
|
||||
|
||||
private:
|
||||
AWP<CShape> m_self;
|
||||
|
||||
CFramebuffer shapeFB;
|
||||
|
||||
int rounding;
|
||||
double border;
|
||||
double angle;
|
||||
CColor color;
|
||||
CHyprColor color;
|
||||
CGradientValueData borderGrad;
|
||||
Vector2D size;
|
||||
Vector2D pos;
|
||||
|
|
@ -32,6 +43,7 @@ class CShape : public IWidget {
|
|||
std::string halign, valign;
|
||||
|
||||
bool firstRender = true;
|
||||
std::string onclickCommand;
|
||||
|
||||
Vector2D viewport;
|
||||
CShadowable shadow;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue