Compare commits

...

153 commits
v0.4.0 ... main

Author SHA1 Message Date
Max Woolf
f7f0c9c6b0
example: Add config location hint to help new users (#771)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-09 18:54:54 +02:00
nyx
c12cf8e509
core: disable fade in when using --immediate (#763)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-07 07:13:41 +00:00
Virt
0c5fd97d61
renderer: properly treat monitor desc: prefix (#765) 2025-05-07 07:12:58 +00:00
Maximilian Seidler
fae1c4f6fe
misc: readme cleanup, remove deps required by hyprgraphics (#762)
Some checks failed
Build / nix (push) Has been cancelled
2025-05-05 23:45:32 +02:00
Maximilian Seidler
e3bd47e177
widgets: add onclick feature (#736)
Some checks are pending
Build / nix (push) Waiting to run
* widget: add click handling and point containment methods to IWidget interface

* core: add onClick method to handle mouse click events

- renderer: move getOrCreateWidgetsFor method declaration to public section

* core: update mouse event handling to track mouse location and button clicks

* widget: add onclick command handling and point containment to CLabel

- config: add onclick special config value to label

* assets: add label configuration for keyboard layout switching

* config: add onclick configuration for label widgets

 - add CLICKABLE macro for onclick configuration
 - replace direct onclick assignment with CLICKABLE macro

* core: fix cursor shape initialization and pointer handling

 - ensure pointer is available before setting cursor shape
 - initialize cursor shape device if not already done

* core: add hover handling and cursor shape updates

 - implement onHover method to manage widget hover states
 - update cursor shape based on hover status
 - ensure all outputs are redrawn after state changes

* widgets: add hover state management and bounding box calculations

 - add setHover and isHovered methods to manage hover state
 - implement containsPoint method for hit testing
 - override getBoundingBox in CLabel for accurate positioning
 - add onHover method in CLabel to change cursor shape

* core: add hover handling in pointer motion

 - invoke onHover method with current mouse location

* widgets: add hover handling and bounding box for password input field

 - add getBoundingBox method to calculate the widget's bounding box
 - implement onHover method to update cursor shape on hover

* widgets: update hover behavior for label widget

 - modify cursor shape setting to only apply when onclickCommand is not empty

* core: optimize hover handling and rendering for lock surfaces

 - Improve hover state tracking for widgets
 - reduce unnecessary redraw calls by tracking hover changes
 - remove redundant renderAllOutputs() call

* widgets: add onclick and hover to shape and image

* core: trigger hover and onclick only for the currently focused surface

* core: handle fractionalScale in onclick and hover

* core: don't trigger onclick or hover when hide_cursor is set

* misc: remove braces

* core: run onclick commands asnychronously

---------

Co-authored-by: Memoraike <memoraike@gmail.com>
2025-05-05 15:11:24 +00:00
Vaxry
6c64630df8
version: bump to 0.8.2
Some checks failed
Build / nix (push) Has been cancelled
2025-05-03 15:04:47 +01:00
Maximilian Seidler
0e3e7206bc
core: use enqueueUnlock for unlocks via SIGUSR1 (#756)
Some checks failed
Build / nix (push) Has been cancelled
2025-04-30 07:12:40 +00:00
Maximilian Seidler
867a71dd78
core: avoid calling wl_display_read_events after poll returned due to EINTR (#757)
Some checks are pending
Build / nix (push) Waiting to run
2025-04-30 06:11:57 +00:00
Maximilian Seidler
82808290d9
core: correct $LAYOUT replacement (#755)
Some checks failed
Build / nix (push) Has been cancelled
* core: remove fake declaration in header

* widgets: fix layout rendering

* core: remove CSeatManager::getActiveKbLayoutName

---------

Co-authored-by: Heorhi Valakhanovich <code@mail.geov.name>
2025-04-25 13:01:34 +00:00
Honkazel
eb28a71756
clang-tidy: fix some errors (#751)
I did c+p .clang-tidy from Hyprland and didn't check it for any errors, lol. Still works though
Thanks to hyprwm/Hyprland#9543
2025-04-21 20:17:15 +02:00
Vaxry
b3f1aa7580
version: bump to 0.8.1 2025-04-17 18:07:52 +01:00
Mihai Fufezan
248dfb09f7
flake.lock: update 2025-04-17 10:18:52 +03:00
Maximilian Seidler
656704aeb0
config: default to center for label halign and valign (#748) 2025-04-15 09:05:42 +00:00
Maximilian Seidler
d953296227
renderer: fix gradient copy size in renderBorder (#742) 2025-04-12 11:47:53 +00:00
Maximilian Seidler
71d35aa75f
image: remove left over raw pointer to COutput (#735) 2025-04-10 07:36:54 +00:00
Vaxry
8f73c39f07 version: bump to 0.8.0 2025-04-08 16:37:50 +01:00
Maximilian Seidler
a8de918cc4
auth: use static for getValue (#732) 2025-04-08 16:37:13 +02:00
Brayden Zee
6daab0517c
fingerprint: update widgets after changing prompt (#730) 2025-04-08 04:49:41 +00:00
Maximilian Seidler
854235e1c8
output: refuse to create session lock surfaces with size 0x0 (#729) 2025-04-07 09:15:41 +00:00
Brayden Zee
dd4c1d5034
fingerprint: allow fprint to suspend and cancel verify for us (#722)
* fingerprint: allow fprint to suspend and cancel verify for us

* fingerprint: fix formatting
2025-04-07 09:14:05 +00:00
Maximilian Seidler
0b1f2a97ef
input-field: decouple outer color and base for hidden input random colors (#727) 2025-04-02 22:15:59 +02:00
Maximilian Seidler
ce1eb7b5f9
core: move fail_timeout from input-field to general (#718) 2025-04-02 22:13:22 +02:00
Maximilian Seidler
1ebbc35c55
core: remove attemptRestoreOnDeath and replace some exits with RASSERT (#720) 2025-03-30 01:33:34 +01:00
davc0n
d9a1625315
assets: update example.conf (#709)
Includes examples from wiki with minor modifications.
Date and time labels inspired by https://github.com/catppuccin/hyprlock

Aims to improve out-of-the-box experience and basic stuff reference.
2025-03-27 08:02:32 +00:00
davc0n
9e54d02590
renderer: remove loading bar (#714) 2025-03-25 06:23:37 +00:00
Maximilian Seidler
f883e669d1
CMake: require wayland-protocols>=1.35 (#713)
tablet-v2 was moved to stable in 1.35. Hyprlock will fail to build if a
earlier version is used.
2025-03-20 08:52:02 +00:00
Maximilian Seidler
ee8ee1f9f7
core: move password buffer clearing to handleInput (#708)
Makes more sense than clearing the input buffer in the auth impl.
Also added a check for the password buffer length to reset the fail
color as soon as the password length > 0.
2025-03-17 11:25:51 +00:00
André Silva
7ab3162d66 nix: mesa -> libgbm
d209d800b7
2025-03-14 08:36:46 +02:00
Maximilian Seidler
9e82fe3547
core: some guards for reconnecting monitors (#704) 2025-03-08 10:58:29 +01:00
Maximilian Seidler
a13b6f0d1a
core: print hyprlock version in the logs (#703) 2025-03-08 10:49:01 +01:00
Maximilian Seidler
78ad1d46b5
label: fix crashes when keymap is a nullptr after suspend (#699) 2025-03-06 08:37:43 +01:00
Maximilian Seidler
cb1c504b38
image: set resourceId in configure (#701)
Fixes a regression caused by #686 (Images don't render cause of a missing resourceId)
2025-03-06 08:25:53 +01:00
Maximilian Seidler
9f37c1c8e9
core: more hyprutils smart pointer usage and safe references to widgets (#686)
* core: move to UP and make widgets use SPs

* widgets: make widgets have a self ref to avoid UB

* fix shadows and let them have a WP to widgets
2025-03-05 08:35:43 +01:00
Maximilian Seidler
712ab62a42
config: make sure disabled animation don't need a valid speed or bezier (#698) 2025-03-05 08:05:19 +01:00
Vaxry
e588351d1d version: bump to 0.7.0 2025-02-22 22:56:54 +00:00
Maximilian Seidler
2a6ec58366
cmake: bump hyprutils version (#693) 2025-02-22 10:55:53 +00:00
Maximilian Seidler
f6e4c1374e
animations: linear bezier for gradient, warp behavior (#689) 2025-02-21 01:10:45 +01:00
Maximilian Seidler
c4b2175822
core: don't rely on the locked event to create lock surfaces dynamically (#687) 2025-02-16 06:29:52 +00:00
Maximilian Seidler
82b63a6930
nix/cmake: make it print the version hash (#683)
* cmake: allow passing commit and version commit

* nix: don't set HYPRLOCK_VERSION_COMMIT

(Nix) Makes it so `--version` always prints the short commit hash, instead of not printing it.
2025-02-10 10:33:54 +00:00
Vaxry
a27585b383 core: add mallopt to modify trim threshold 2025-02-09 17:39:23 +00:00
Mihai Fufezan
ad7600dca9
CI: remove deprecated magic-nix-cache-action 2025-02-08 23:07:22 +02:00
Honkazel
dc6d72158c
core: clang-tidy and comp fixes (#679)
* clang-tidy and comp fixes

* nit changes
2025-02-06 11:36:08 +00:00
Maximilian Seidler
ce750456f9
fingerprint: handle failed dbus connection (#676) 2025-02-05 09:36:31 +00:00
Maximilian Seidler
ec82da7108
input-field: improve dynamic width (#647)
* input-field: fixup dynamic width

- Instead of not rendering placeholder text,
  if it does not fit into the input-field width,
  render it up until the input field size.
- Improve updateWidth.

* input-field: make sure dots center does not change during width anim
2025-02-04 10:31:12 +00:00
IChengHo
465148ac21
input-field: Fix crash when numlock is on and numlock_color is fallback (#673)
When numlock is enabled but numlock_color is fallback, `targetGrad`
remains nullptr. This causes Hyprlock to crash in updateColors().

This commit aligns the condition check for assigning `targetGrad` with
later usages.
2025-02-03 18:28:35 +00:00
Maximilian Seidler
c976b6a1d1
input-field: fix color updates for BORDERLESS swap_font_color false (#669) 2025-01-30 11:03:17 +00:00
Maximilian Seidler
1bfa79eb83
core: move to hyprlang config value wrapper (#667) 2025-01-29 23:10:27 +01:00
Maximilian Seidler
e77bc92b99
core: don't attempt to unlock when we are not locked yet (#661) 2025-01-25 21:43:21 +01:00
Jan Beich
408ce95dd0
widgets: chase libc++ < 19 compat after 2c5ae4d661 (#659)
src/renderer/widgets/IWidget.cpp:123:11: error: no type named 'time_zone' in namespace 'std::__1::chrono'; did you mean 'date::time_zone'?
  123 |     const std::chrono::time_zone* pCurrentTz = nullptr;
      |           ^~~~~~~~~~~~~~~~~~~~~~
      |           date::time_zone
/usr/include/date/tz.h:785:7: note: 'date::time_zone' declared here
  785 | class time_zone
      |       ^
src/renderer/widgets/IWidget.cpp:127:39: error: no member named 'locate_zone' in namespace 'std::__1::chrono'
  127 |             pCurrentTz = std::chrono::locate_zone(name);
      |                          ~~~~~~~~~~~~~^
2025-01-24 17:31:43 +01:00
Maximilian Seidler
07b5e1b4cd
core: fix background screenshot on nvidia (#656)
Fixes DMA buffer screencopy on nvidia cards. Additionally adds shm screencopy as an option
2025-01-24 14:25:37 +01:00
Mihai Fufezan
742eb98c6a
flake.lock: update 2025-01-23 14:31:33 +02:00
Maximilian Seidler
d547d1d4e3
core: move wayland event reading into the poll thread (#655)
This was done, so that we can

  wl_display_prepare_read -> poll -> wl_display_read_events

That fixes synchronization issues on nvidia proprietary drivers.
2025-01-21 13:42:11 +00:00
Maximilian Seidler
02639c2759
animation: small gradient fail transition fixup (#648) 2025-01-15 16:09:11 +01:00
Maximilian Seidler
4f964371cc
auth: fixup prompt and fail substitution (#641)
BREAKING:
- Removed $PROMPT variable. Either use $PAMPROMPT or $FPRINTPROMPT.
- Removed $FPRINTMESSAGE. Use $FPRINTPROMPT instead. There is also
  $FPRINTFAIL.
2025-01-12 17:18:18 +00:00
pastalian
023aff52ad
cmake: explicitly require GLES3 component (#645)
This allows building on a system that:
- Lack GLX support
- Require explicit declaration for OpenGL::EGL

Reference: 0cc144ecfd/Tests/FindOpenGL/Test/CMakeLists.txt (L127-131)
2025-01-11 16:38:18 +00:00
Maximilian Seidler
e84267085d
animation: allow adding vars during ::tick (#644) 2025-01-11 16:36:18 +00:00
izmyname
a5e346783f
examples: Add default animations to the example config (#643) 2025-01-10 13:15:27 +00:00
Maximilian Seidler
73e23e535f
animations: fix overshoot beziers and cleanup animation config parsing (#642) 2025-01-09 20:59:16 +00:00
davc0n
de844d39ad
helpers: fix absolutePath relative with tilde (#640)
Relative path was not handled if input started with tilde.
Examples:
	~/./test
	~/weird/../test

No reason to do something like this imho, but users you never know.
2025-01-06 13:18:13 +00:00
Maximilian Seidler
00d2cbfee3
core: introduce animation manager and animation config (#631)
BREAKING:
- Removed `input-field:dots_fade_time`. Now configured via
`animation=inputFieldDots,...`
- Removed `input-field:fail_transition`. Now configured via
`animation=inputFieldColors,...`
- Removed `general:no_fade_in` and `general:no_fade_out`. Now configured
globally via `animations:enabled` or via `animation=fadeIn,...` and
`animation=fadeOut,...`
2025-01-06 12:34:21 +00:00
vaxerski
8f68fad50a core: bind to wl_seat v8
fixes #638
2025-01-04 17:32:44 +01:00
Maximilian Seidler
c3d95953c0
output: fix setting transform (#636) 2025-01-03 22:57:16 +00:00
Brayden Zee
e01afaf107
fingerprint: add a delay after an unrecognized fingerprint (#625) 2025-01-01 20:48:32 +00:00
Maximilian Seidler
a2f00fb735
config: fix gradient rgb(a) parsing with spaces (for real this time) (#628)
* config: fix gradient rgb(a) parsing with spaces (for real this time)

* config: conditionally split gradient colors
2025-01-01 12:18:37 +00:00
Jan Beich
836dbfbb13
core: add missing header for BSDs after 753c538dea (#630)
src/core/Seat.cpp:76:17: error: use of undeclared identifier 'close'
   76 |                 close(fd);
      |                 ^
2024-12-30 18:40:59 +00:00
davc0n
2c5ae4d661
widgets: add TZ env var support for $TIME (#627)
* widgets: add TZ env var support for $TIME

* widgets: refactor getTime if else brackets
2024-12-30 15:32:46 +00:00
Maximilian Seidler
3d63d9b129
input-field: don't change outer color when numlock_color is not set (#621)
* input-field: don't change outer color when numlock_color is not set

* input-field: same for bothlock_color
2024-12-29 18:38:16 +00:00
Vaxry
753c538dea
Core: move to hyprwayland-scanner (#624)
nix: add hyprwayland-scanner dep

flake.lock: update

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-12-29 18:36:08 +00:00
vaxerski
77d194c1e9 core: clean up pointer logic and clang-format 2024-12-29 16:40:01 +01:00
Maximilian Seidler
d94cc3a5ab
widgets: algin rect outline radius with inner border radius (#614)
* widget: add utility functions to calculate borders and make em static

* image: use the rounding util functions

* input-field: use the rounding util functions

* shape: use the rounding util functions
2024-12-29 11:14:49 +00:00
Maximilian Seidler
820eaff7a8
misc: print the correct commit hash (#619) 2024-12-29 08:24:25 +00:00
Ricky Lopez
d212f4cc10
fingerprint: better feedback and don't clear input field on retry (#613) 2024-12-27 15:21:02 +00:00
Maximilian Seidler
181294c4d8
widgets: use absolutePath to get last_write_time and ignore when no reloadTime (#615)
* image: use absolutePath to get last_write_time and ignore when no reloadTime

* background: use absolutePath to get last_write_time and ignore when no reloadTime
2024-12-26 15:43:11 +00:00
Maximilian Seidler
90bc9764f1
input-field: use IWidget::formatString for placeholder text (#603) 2024-12-24 22:38:39 +00:00
Maximilian Seidler
bf37645daa
auth: assert username (#604) 2024-12-23 20:53:27 +00:00
Maximilian Seidler
5361dc40da
misc: move to std::print (#592) 2024-12-22 15:09:20 +00:00
Maximilian Seidler
002e44f627
label: fix redrawing shadow on label updates (#599) 2024-12-22 15:53:52 +01:00
Maximilian Seidler
83bda4883d input-field: improve behavior of *lock colors (#596) 2024-12-22 13:37:22 +00:00
Maximilian Seidler
8a9f05fa1f
core: fix parsing grad with decimal rgb(a) colors (#595)
* Revert "config: use removeEmpty for the gradient varlist (#565)"

This reverts commit 578246b996.

* config: ignore empty grad components
2024-12-22 13:56:02 +01:00
Mihai Fufezan
f2c153c09b
nix: pass commit info to cmake 2024-12-21 23:20:38 +02:00
Maximilian Seidler
ee37e41723
misc: add git commit hash to --version when not on tag v${VERSION} (#590) 2024-12-20 19:41:06 +01:00
Jan Beich
d12b4a7fba
pam: add missing header for libc++ after a4b0562749 (#589)
In file included from src/auth/Auth.cpp:2:
src/auth/Pam.hpp:43:5: error: no type named 'thread' in namespace 'std'; did you mean 'pthread'?
   43 |     std::thread           m_thread;
      |     ^~~~~~~~~~~
      |     pthread
2024-12-18 20:10:01 +01:00
Robin Carlier
058830668e
widgets: Reload backgrounds and not just images (#583)
* widgets: Reload background and fade

* clang-format

also clang-format

* undo possibly unwanted format on Renderer.hpp

* rename stuff + style

* codestyle + initialize reloadTime

* remove trailing eols
2024-12-18 16:28:05 +01:00
Vaxry
381a284b3b version: bump to 0.6.0 2024-12-18 15:03:04 +00:00
Maximilian Seidler
fe35a8068c
core: terminate auth after recieving finished (#586) 2024-12-18 15:52:35 +01:00
Austin Horstman
61be0cb652
nix/overlays: gcc13 -> gcc14; flake.lock: update (#584)
* nix/overlays: gcc13 -> gcc14

* flake.lock: update
2024-12-16 22:24:00 +01:00
Maximilian Seidler
a4b0562749
auth: add an interface for different authentication methods (#578)
* auth: add an interface for different authentication methods

* auth: pick inline feedback based on last active implementation

* config: move auth options to auth:<auth_impl>

BREAKING:
- general:pam_module -> auth:pam:module
- general:enable_fingerprint -> auth:fingerprint:enabled
- general:fingerprint_ready_message -> auth:fingerprint:ready_message
- general:fingerprint_present_message ->
auth:fingerprint:present_message

* auth: don't clear password input for fingerprint auth check

* fingerprint: checkAuthenticated when handling verfiy status

* Revert conditionally clearing the password input buffer

Makes sure the input field can show the fail text for fingerprint auth.

* auth: virtual instead of override, remove braces

* pam: join the thread

* auth: remove isAuthenticated and switch to a control flow based unlock

* auth: initialize authentication before aquiring the session lock
2024-12-16 19:58:36 +01:00
Maximilian Seidler
4681f8f7f3
input-field: fix width animations (#582) 2024-12-16 01:00:41 +01:00
Maximilian Seidler
8010b81e7b
core: move to Hyprutils::OS::CProcess for spawning processes (#575)
* core: move to Hyprutils::OS::CProcess for spawning processes

* nix: flake update
2024-12-08 16:42:16 +01:00
Maximilian Seidler
cc7ffe73e7
renderer: round boxes (#571)
* renderer: round boxes before matrix projection

* renderer: use rounded boxes throughout
2024-12-01 17:13:55 +00:00
Vaxry
4667f721be
Core: move to hyprgraphics (#570)
* core: move to hyprgraphics

* Nix: add hyprgraphics

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-11-28 16:43:11 +00:00
Maximilian Seidler
578246b996
config: use removeEmpty for the gradient varlist (#565) 2024-11-19 13:35:53 +00:00
Maximilian Seidler
b9cf5151ba
input-field: fix colorConfig caps, num and both when they are empty (#559) 2024-11-15 00:45:55 +00:00
Maximilian Seidler
d159e14e5b
config: make configStringToInt support rgb(a) with decimal values (#558) 2024-11-15 00:43:47 +00:00
Maximilian Seidler
2775ab2868
widgets: render frambuffers with alpha 1.0 (#554)
* helpers: update CColor

yoinked from Hyprland

* image: fix border alpha

* shape: fix border alpha
2024-11-12 13:31:27 +00:00
Vaxry
c3c28feb4c widget: show 12AM instead of 0AM 2024-11-11 15:58:49 +00:00
Vaxry
eadcbc0aed widget: fix time12 when it's 12pm
fixes #552
2024-11-11 15:58:11 +00:00
Maximilian Seidler
d8bd25b52d
core: add support for composed keys (#551) 2024-11-11 15:49:51 +00:00
Maximilian Seidler
6c3c444136
core: add border shader and border gradients (#548)
* renderer: add renderBorder function

* config: add CGradientValueData from Hyprland

* input-field: change outer to take gradients

Added gradient support to the following color options:
- `outer_color`
- `fail_color`
- `check_color`
- `capslock_color`
- `numlock_color`
- `bothlock_color``

* image: add gradient border

* shape: add gradient border

* shaders: adapt the new rounded smoothing factor from Hyprland
2024-11-09 16:54:44 +00:00
Maximilian Seidler
4fc133c96f
widgets: add support for specifing size and position options via percentages of output dimensions (#541)
* config: introduce a custom value type for layout related options

* widgets: use CLayoutValueData for size and position options

* widgets: catch bad_any_cast and out_of_range when contructing widgets other than label

* config: rename and restrict CLayoutValueData::fromAny to fromAnyPv

This is only for casting `any` variables that represent a void * to a
CLayoutValueData*, not just any any.

* misc: remove debug prints
2024-11-06 16:50:42 +00:00
moggiesir
1cd3231537
auth: make fingerprint initialization async (#544) 2024-11-05 13:12:26 +00:00
Maximilian Seidler
f225e23e5b
misc: make Debug::log flush stdout (#542)
Makes it a lot easier to spot a failed RASSERT
2024-11-02 23:42:39 +00:00
Maximilian Seidler
edbecc8708
widgets: remove debug remnants from #527 (#532) 2024-10-27 18:33:05 +00:00
Maximilian Seidler
29dd33d6a4
widgets: check current_zone pointer (#527)
* widgets: check current_zone pointer and fallback to utc

* misc: remove redundant printf in RASSERT

* widgets: no curly else
2024-10-26 19:27:24 +01:00
Maximilian Seidler
ae3bb0fd43
input-field: fix invert_numlock regression (#530) 2024-10-25 22:04:56 +01:00
Maximilian Seidler
f13d97e6d6
config: make the default widget position be 0,0 (#529) 2024-10-25 22:02:47 +01:00
Vaxry
a093a9eefd version: bump to 0.5.0 2024-10-22 01:09:26 +01:00
moggiesir
f48540fcd4
auth: Support parallel fingerprint auth (#514)
* auth: Support parallel fingerprint auth

I chose to use Fprint's dbus interface directly rather than going through pam (which uses Fprint's dbus interface) due to poor handling of system sleep somewhere between fprintd and pam. When preparing for sleep, fprintd puts the device to sleep, which causes VerifyStatus to emit with verify-unknown-error, which normally should be responded to by calling both Device.StopVerify and Device.Release (and this is what pam does). Unfortunately, if you try to release the device when the system is preparing for sleep, you'll get an error that the device is busy and then you can't can't claim or release the device for 30 seconds.

pam also has a max timeout for pam_fprintd.so of 99 seconds, and so if we used pam, we'd have to deal with the timeouts and keep restarting the auth conversation.

gdm/gnome-session lock seems to get around these issues by having a shutter on top of the lock screen that you have to interact with first that gives gnome-session a trigger to start fingerprint auth.

* nix/overlays: add sdbus overlay

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-10-22 01:08:24 +01:00
André Silva
b808086286
renderer: log framebuffer creation as trace (#518) 2024-10-17 15:23:58 +01:00
Vaxry
11694528b4
README: update deps 2024-10-13 13:05:11 +01:00
Maximilian Seidler
5065788a47
misc: use Vector2D, Box and Mat3x3 from hyprutils (#515)
* misc: use Vector2D, Box and Mat3x3 from hyprutils

* nix: flake update

Fix CI fails. We need hyprutils>=0.2.3

* misc: use a function to convert Hyprlang::VEC2 to Vector2D

* misc: fixup some includes
2024-10-13 13:04:32 +01:00
Maximilian Seidler
71021cc3de
input-field: add dots_text_format to support setting arbitrary chars as the input indicator (#510)
* input-field: make dots support arbitrary chars

* input-field: add font_familiy for placeholder and text dots

* input-field: allow dots_spacing from -1.0 to 1.0

Useful when using emojis in  dots_text_format
2024-10-13 00:16:31 +01:00
Maximilian Seidler
7362ce3435
misc: use hyprutils for string replaceAll (#512) 2024-10-11 16:44:47 +01:00
The-Emperor10
264fb8b70f
config: add input-field dots_fade_time option (#508)
* config: add input-field dots_fade_time option
Time is in milliseconds. Anything <= 0 is immediate.

* input-field: change speedPerSecond to fadeMs
Default is 200ms, which is the same amount of time as the previous
5 speedPerSecond. Not sure using doubles is necessary, but I'm
using them to avoid precision issues.

Closes: https://github.com/hyprwm/hyprlock/issues/355
2024-10-10 22:20:14 +01:00
Maximilian Seidler
eb63207ef0
core: make attemptRestoreOnDeath faster and hyprland exclusive (#506)
* core: make attemptRestoreOnDeath hyprland exclusive

* core: destoy lock and sessionLockSurfaces in attemptRestoreOnDeath
2024-10-05 14:30:19 +01:00
davc0n
d9c2a5e0b7
widgets: add 12h time format (#500) 2024-09-30 12:22:24 +01:00
Maximilian Seidler
9ea804788c
core: set capslock and numlock states on startup (#496) 2024-09-25 09:56:20 +01:00
Yang, Ying-chao
153977aab3
asyncResourceGatherer: stop worker threads when application is going to exit (#481) 2024-09-05 12:27:43 +01:00
Yang, Ying-chao
0b030d33c8
asyncResourceGatherer: do not detach worker threads (#477)
Worker threads become non-joinable once they are detached, and `await()` will not wait for them to
finish. This can lead to a crash when `asyncResourceGatherer` is destroyed in the main thread while it
is still being used in worker threads.
2024-09-04 20:58:38 +01:00
André Silva
73b0fc26c0 nix: add wayland-scanner native build input 2024-09-01 18:19:54 +03:00
Darko Nikolić
cc71c0b7d9
core: fix symlinked images not picking up extension (#473) 2024-08-31 12:37:47 +02:00
Maximilian Seidler
9c1e9e7db2
core: support desc: prefix for widget monitor options (#470) 2024-08-29 12:36:07 +02:00
Maximilian Seidler
a0af542f9b
input-field: refactor updateColors and other improvements (#469)
* input-field: refactor updateColors

* input-field: fix input-field:invert_numlock

* input-field: use updatePlaceholder to request the initial placeholder asset

* input-field: allow more gradual color animations

* input-field: fix caps and num indicator colors, when borderless and swap_font_color is true
2024-08-29 09:53:00 +02:00
Yang, Ying-chao
7bb4113a7e
core: fix crash caused by exiting without joining running thread (#464) 2024-08-21 11:25:14 +01:00
Maximilian Seidler
f673759d01
lockSurface: fix dynamic output mode and scale updates (#462)
* lockSurface: reload widgets on output change

* lockSurface: only configure when scale actually changed

* lockSurface: enable fsv1 per default for all compositors
2024-08-18 08:19:56 +01:00
Maximilian Seidler
9393a3e94d
core: add fractional_scaling option (#456)
* config: add fractional_scaling option

0 -> off
1 -> on
2 -> auto

* core: default auto option for fractional_scaling

* locksurface: fallback to integer scaling
2024-08-05 20:22:01 +02:00
alba4k
8cffe0618c fix alignment in help message 2024-08-04 12:53:43 +03:00
Aaron Blasko
5d85ea03b0
renderer: add --no-fade-in (#453) 2024-08-03 17:34:54 +02:00
Mihai Fufezan
c7fa5026c0
assets: add example.conf 2024-08-02 21:39:54 +03:00
Maximilian Seidler
8a89181e69
auth: use pam_faillock log as $FAIL (#447)
Allows us to show "(x minutes left to unlock)" directly in the
input-field fail text.
2024-07-30 18:52:50 +02:00
davc0n
5e8f12c2d9
core: minor refactor of command line options parsing (#441) 2024-07-28 19:10:36 +01:00
Maximilian Seidler
cf0e975fed
widgets: move asset updates out of the draw function (#442)
* label: move asset updates out of the draw function

This allows us to check if the label has actually updated after the
callback from the asyncResourceGatherer. If it isn't we can requeue the
renderUpdate() function.

* image: move asset updates out of the draw function
2024-07-24 23:18:11 +02:00
Maximilian Seidler
3e71799b30
misc: add issue templates (#440)
* misc: add issue templates

Copied from Hyprland and slightly modified

* misc: but template --version and minor improvements

* misc: fix bug template closing tag
2024-07-24 17:52:15 +02:00
Vaxry
58e1a4a499 core: add --version 2024-07-24 13:58:59 +02:00
Vaxry
dba9d8b517 version: bump to 0.4.1 2024-07-21 14:00:28 +02:00
Maximilian Seidler
3d3b52e42c
asyncResourceGatherer: deduplicate image rendering code (#433)
* asyncResourceGatherer: deduplicate image rendering code

Also happens to add support for JPEG and WEBP for image widgets

* asyncResourceGatherer: use a reference for path in getFileType
2024-07-19 23:20:31 +02:00
Maximilian Seidler
20c01d91d4
core: make sure m_sLockState.lock is present in onLockFinished and releaseSessionLock (#432) 2024-07-19 17:26:38 +02:00
Mihai Fufezan
372a4cd55e
flake.lock: update 2024-07-18 20:46:51 +03:00
Mihai Fufezan
a29012cbbd
CMake: fmt 2024-07-18 20:46:31 +03:00
Mihai Fufezan
cac93f67e8
CMake, Nix: add VERSION file 2024-07-18 20:45:42 +03:00
Maximilian Seidler
9514925a7c
core: grace unlock improvements and auth fixes for grace/SIGUSR1 unlocks (#424)
* core: check m_bTerminate for grace unlocks

* core: remove reference to the lock object on finished

* core: add isUnlocked

true if m_bFadeStarted or m_bTerminate

* auth: return early on grace or SIGUSR1 unlocks
2024-07-17 15:22:42 +02:00
Mihai Fufezan
a3d8a2c128
Config: use hyprutils helper (#422)
* flake.lock: update

* gitignore: add CMake residual files

* config: use hyprutils helper

* CMake: update required hyprutils version
2024-07-16 22:35:48 +02:00
Maximilian Seidler
69d37d2663
core: immediately create session lock surfaces (#421)
* core: immediately create session lock surfaces

Instead of waiting for the `locked` event, create session lock surfaces
right away.

* core: don't allow unlock_and_destroy if `locked` has never been recieved
2024-07-14 16:59:06 +02:00
Maximilian Seidler
b407128cae
core: handle ext_session_lock_v1::finished as defined in the protocol (#418) 2024-07-11 16:15:32 +02:00
Maximilian Seidler
e5f0b56d07
asyncResourceGatherer: trace logs and exit behaviour (#414)
* asyncResourceGatherer: add trace logs

useful for debugging label updates

* label: remove unused onAssetCallbackTimer

* asyncResourceGatherer: fix crashes on exit
2024-07-10 12:05:18 +02:00
davc0n
944caff79f
config: add input-field fail_timeout option (#406)
* config: add input-field fail_timeout option

* config: change input-field fail_timeout to milliseconds

* input-field: fix configFailTimeoutMs type and init
2024-07-09 17:43:31 +02:00
Jasson
d8ccc6f96a
core: Label exception handling + Frambuffer checks + headers (#413)
* Added exception handling in label constructor

* Framebuffer fix + moved headers

* added optional header
2024-07-09 11:32:49 +02:00
Maximilian Seidler
3bedae4436
auth: don't start pam conversation before the initial input happens (#409)
After realizing that pam modules sometimes implement a timeout, i think
it is not worth starting the convo it right away.
Now you won't get the initial PAM_PROMPT any more.
Prompt will be initialized to "Password: ", which is most commonly what
you get from pam. Subsequent prompts (e.g. 2fa) will be handled however.
2024-07-08 14:25:06 +02:00
Davide Conti
e72f601209 readme: add official repo package to arch install 2024-07-08 14:15:10 +03:00
Maximilian Seidler
43f2b7441b
core: handle missing wayland protocol support (#408)
* core: check support of wp_factional_scale_manager_v1 and wp_viewporter

* core: check support of zwlr_screencopy_manager_v1
2024-07-07 21:44:53 +02:00
Maximilian Seidler
0552a1eddd
core: add option to render solid background immediatly when bg assets are not ready (#407)
* asyncResourceGatherer: start the asyncLoop at the same time as gather

This is a prerequesit for labels beeing drawn, while backgrounds are
note ready yet.

* core: allow immediate rendering even when backgrounds are not gathered yet

Note:
We don't really need to call `asyncResourceGatherer::apply` in the
`renderLock` function, since it will get called by a call to
`asyncResourceGatherer::getAssetById` anyways.

* background: render color rectangle when asset is not ready yet

* config: add general:immediate_render config option

* core: use the --immediate-render flag in attemptRestoreOnDeath
2024-07-07 18:43:17 +02:00
Jasson
a50296c181
core: minor bugfixes (#405) 2024-07-07 17:55:59 +02:00
Tom Englund
7fb3c03500
hyprlock: ensure members are accessed direct (#404)
if the unique_ptr is reset directly accessing it from the destructor
causes crashes on Clang/libc++, just access the members directly.
2024-07-06 12:24:29 +02:00
85 changed files with 5671 additions and 3205 deletions

101
.clang-tidy Normal file
View file

@ -0,0 +1,101 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

104
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View file

@ -0,0 +1,104 @@
name: Bug Report
description: Something is not working right
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
## Before opening a new issue, please take a moment to search through the current open and closed issues to check if it already exists.
---
- type: dropdown
id: type
attributes:
label: Regression?
description: |
Regression means that something used to work but no longer does.
**BEFORE CONTINUING**, please check if this bug is a regression or not, and if it is, we need you to bisect with the help of the wiki: https://wiki.hyprland.org/Crashes-and-Bugs/#bisecting-an-issue
multiple: true
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: ver
attributes:
label: Hyprlock Info and Version
description: |
Please enter your hyprlock version and config.
Use `hyprlock --version` if it is available (Introduced after v0.4.1).
Otherwise please figure out the hyprlock version via your distribution.
If you built it yourself, post the commit hash.
Provide your hyprlock config. Usually `~/.config/hypr/hyprlock.conf`.
value: "<Enter your hyprlock version here>
<details>
<summary>Hyprlock config</summary>
```sh
<Paste your hyprlock config here>
```
</details>"
validations:
required: true
- type: textarea
id: compositor
attributes:
label: Compositor Info and Version
description: |
If you are on hyprland, paste the output of `hyprctl systeminfo` here.
If you are on another compositor, enter the compositor name and version you are on.
value: "<details>
<summary>System/Version info</summary>
```sh
<Paste the output of the command here>
```
</details>"
validations:
required: true
- type: textarea
id: desc
attributes:
label: Description
description: "What went wrong?"
validations:
required: true
- type: textarea
id: repro
attributes:
label: How to reproduce
description: "How can someone else reproduce the issue?"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Crash reports, logs, images, videos
description: |
Anything that can help. Please always ATTACH and not paste them.
To get hyprlock logs, start it from the commandline.
If you are using hypridle/swayidle as a systemd service, you can use `journalctl -b0 --user -u hypridle/swayidle` to get some logs.
Compositor logs and crashes are sometimes relevant as well.
Hyprland logs can be found in $XDG_RUNTIME_DIR/hypr.
Hyprland crash reports are stored in ~/.cache/hyprland or $XDG_CACHE_HOME/hyprland.

19
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Feature Request
description: I'd like to request additional functionality
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before opening a new issue, take a moment to search through the current open ones.
---
- type: textarea
id: desc
attributes:
label: Description
description: "Describe your idea"
validations:
required: true

View file

@ -8,7 +8,6 @@ jobs:
- uses: actions/checkout@v3
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
# not needed (yet)
# - uses: cachix/cachix-action@v12

11
.gitignore vendored
View file

@ -1,9 +1,14 @@
.cache
**/.cache
.direnv
.envrc
.vscode/
CMakeCache.txt
CMakeFiles/
Makefile
cmake_install.cmake
build/
compile_commands.json
protocols/*.c
protocols/*.h
protocols/*.cpp
protocols/*.hpp
*.kdev4
.gdb_history

View file

@ -1,34 +1,66 @@
cmake_minimum_required(VERSION 3.19)
cmake_minimum_required(VERSION 3.27)
set(VERSION 0.4.0)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VERSION)
project(hyprlock
DESCRIPTION "A gpu-accelerated screen lock for Hyprland"
VERSION ${VERSION}
)
project(
hyprlock
DESCRIPTION "A gpu-accelerated screen lock for Hyprland"
VERSION ${VERSION})
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprlock in Debug with CMake")
add_compile_definitions(HYPRLAND_DEBUG)
message(STATUS "Configuring hyprlock in Debug with CMake")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprlock in Release with CMake")
add_compile_options(-O3)
message(STATUS "Configuring hyprlock in Release with CMake")
endif()
include_directories(
.
"protocols/"
)
include_directories(. "protocols/")
include(GNUInstallDirs)
# configure
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
-Wno-missing-field-initializers -Wno-narrowing)
-Wno-missing-field-initializers -Wno-narrowing)
add_compile_definitions(HYPRLOCK_VERSION="${VERSION}")
if (DEFINED HYPRLOCK_COMMIT)
add_compile_definitions(HYPRLOCK_COMMIT="${HYPRLOCK_COMMIT}")
else()
# get git commit
execute_process(
OUTPUT_VARIABLE GIT_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND git rev-parse --short HEAD
)
add_compile_definitions(HYPRLOCK_COMMIT="${GIT_SHORT_HASH}")
endif()
if (DEFINED HYPRLOCK_VERSION_COMMIT)
add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${HYPRLOCK_VERSION_COMMIT}")
else()
# get git commit of v$VERSION
execute_process(
OUTPUT_VARIABLE GIT_TAG_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND git rev-parse --short v${VERSION}
)
add_compile_definitions(HYPRLOCK_VERSION_COMMIT="${GIT_TAG_SHORT_HASH}")
endif()
message(STATUS "VERSION COMMIT: ${HYPRLOCK_VERSION_COMMIT}")
message(STATUS "COMMIT: ${HYPRLOCK_COMMIT}")
# position independent build: __FILE__
add_compile_options(-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
@ -38,53 +70,83 @@ message(STATUS "Checking deps...")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon libjpeg libwebp libmagic cairo pangocairo libdrm gbm hyprutils>=0.1.1)
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
wayland-client
wayland-protocols>=1.35
wayland-egl
hyprlang>=0.6.0
egl
xkbcommon
cairo
pangocairo
libdrm
gbm
hyprutils>=0.5.0
sdbus-c++>=2.0.0
hyprgraphics)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps OpenGL::EGL OpenGL::GL)
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
OpenGL::EGL OpenGL::GLES3)
# protocols
find_program(WaylandScanner NAMES wayland-scanner)
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
execute_process(
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
message(
STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
function(protocol protoPath protoName external)
if (external)
execute_process(
COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
else()
execute_process(
COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
endif()
function(protocolnew protoPath protoName external)
if(external)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
else()
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
endif()
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}.cpp
protocols/${protoName}.hpp)
endfunction()
function(protocolWayland)
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
COMMAND hyprwayland-scanner --wayland-enums --client
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
endfunction()
make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so the dir won't be there
protocol("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false)
protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false)
protocol("unstable/tablet/tablet-unstable-v2.xml" "tablet-unstable-v2" false)
protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false)
protocol("stable/viewporter/viewporter.xml" "viewporter" false)
protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true)
protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false)
make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so
# the dir won't be there
protocolwayland()
protocolnew("protocols" "wlr-screencopy-unstable-v1" true)
protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false)
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
protocolnew("stable/viewporter" "viewporter" false)
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
protocolnew("stable/tablet" "tablet-v2" false)
# Installation
install(TARGETS hyprlock)
install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d)
install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d)
install(
FILES ${CMAKE_SOURCE_DIR}/assets/example.conf
DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr
RENAME hyprlock.conf)

View file

@ -2,10 +2,14 @@
Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility.
## Features
- uses the secure ext-session-lock protocol
- full support for fractional-scale
- fully GPU accelerated
- multi-threaded resource acquisition for no hitches
- Uses the ext-session-lock protocol
- Support for fractional-scale
- Fully GPU accelerated
- Multi-threaded resource acquisition
- Blurred screenshot as the background
- Native fingerprint support (using libfprint's dbus interface)
- Some of Hyprland's eyecandy: gradient borders, blur, animations, shadows, etc.
- and more...
## How it looks
@ -16,30 +20,32 @@ Hyprland's simple, yet multi-threaded and GPU-accelerated screen locking utility
## Arch install
```sh
yay -S hyprlock-git
pacman -S hyprlock # binary x86 tagged release
# or
yay -S hyprlock-git # compiles from latest source
```
## Building
### Deps
You also need the following dependencies
You need the following dependencies
- cairo
- hyprgraphics
- hyprland-protocols
- hyprlang
- hyprutils
- hyprwayland-scanner
- mesa (required is libgbm, libdrm and the opengl runtime)
- pam
- pango
- sdbus-cpp (>= 2.0.0)
- wayland-client
- wayland-protocols
- mesa
And the development libraries for the following
- cairo
- libdrm
- pango
- xkbcommon
- pam
- hyprlang >= 0.4
- libmagic (file-devel on Fedora)
Development libraries are usually suffixed with `-devel` or `-dev` in most distro repos.
You also need to install `mesa-libgbm-devel` on some distros like RPM based ones where its not
bundled with the mesa package.
Sometimes distro packages are missing required development files.
Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`.
### Building

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.8.2

106
assets/example.conf Normal file
View file

@ -0,0 +1,106 @@
# sample hyprlock.conf
# for more configuration options, refer https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock
#
# rendered text in all widgets supports pango markup (e.g. <b> or <i> tags)
# ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#general-remarks
#
# shortcuts to clear password buffer: ESC, Ctrl+U, Ctrl+Backspace
#
# you can get started by copying this config to ~/.config/hypr/hyprlock.conf
#
$font = Monospace
general {
hide_cursor = false
}
# uncomment to enable fingerprint authentication
# auth {
# fingerprint {
# enabled = true
# ready_message = Scan fingerprint to unlock
# present_message = Scanning...
# retry_delay = 250 # in milliseconds
# }
# }
animations {
enabled = true
bezier = linear, 1, 1, 0, 0
animation = fadeIn, 1, 5, linear
animation = fadeOut, 1, 5, linear
animation = inputFieldDots, 1, 2, linear
}
background {
monitor =
path = screenshot
blur_passes = 3
}
input-field {
monitor =
size = 20%, 5%
outline_thickness = 3
inner_color = rgba(0, 0, 0, 0.0) # no fill
outer_color = rgba(33ccffee) rgba(00ff99ee) 45deg
check_color = rgba(00ff99ee) rgba(ff6633ee) 120deg
fail_color = rgba(ff6633ee) rgba(ff0066ee) 40deg
font_color = rgb(143, 143, 143)
fade_on_empty = false
rounding = 15
font_family = $font
placeholder_text = Input password...
fail_text = $PAMFAIL
# uncomment to use a letter instead of a dot to indicate the typed password
# dots_text_format = *
# dots_size = 0.4
dots_spacing = 0.3
# uncomment to use an input indicator that does not show the password length (similar to swaylock's input indicator)
# hide_input = true
position = 0, -20
halign = center
valign = center
}
# TIME
label {
monitor =
text = $TIME # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#variable-substitution
font_size = 90
font_family = $font
position = -30, 0
halign = right
valign = top
}
# DATE
label {
monitor =
text = cmd[update:60000] date +"%A, %d %B %Y" # update every 60 seconds
font_size = 25
font_family = $font
position = -30, -150
halign = right
valign = top
}
label {
monitor =
text = $LAYOUT[en,ru]
font_size = 24
onclick = hyprctl switchxkblayout all next
position = 250, -20
halign = center
valign = center
}

View file

@ -1,5 +1,31 @@
{
"nodes": {
"hyprgraphics": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1743953322,
"narHash": "sha256-prQ5JKopXtzCMX2eT3dXbaVvGmzjMRE2bXStQDdazpM=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "9d7f2687c84c729afbc3b13f7937655570f2978d",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprgraphics",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
@ -13,11 +39,11 @@
]
},
"locked": {
"lastModified": 1717881852,
"narHash": "sha256-XeeVoKHQgfKuXoP6q90sUqKyl7EYy3ol2dVZGM+Jj94=",
"lastModified": 1744468525,
"narHash": "sha256-9HySx+EtsbbKlZDlY+naqqOV679VdxP6x6fP3wxDXJk=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "ec6938c66253429192274d612912649a0cfe4d28",
"rev": "f1000c54d266e6e4e9d646df0774fac5b8a652df",
"type": "github"
},
"original": {
@ -36,11 +62,11 @@
]
},
"locked": {
"lastModified": 1717881334,
"narHash": "sha256-a0inRgJhPL6v9v7RPM/rx1kbXdfe3xJA1c9z0ZkYnh4=",
"lastModified": 1743950287,
"narHash": "sha256-/6IAEWyb8gC/NKZElxiHChkouiUOrVYNq9YqG0Pzm4Y=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "0693f9398ab693d89c9a0aa3b3d062dd61b7a60e",
"rev": "f2dc70e448b994cef627a157ee340135bd68fbc6",
"type": "github"
},
"original": {
@ -49,13 +75,36 @@
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1739870480,
"narHash": "sha256-SiDN5BGxa/1hAsqhgJsS03C3t2QrLgBT8u+ENJ0Qzwc=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "206367a08dc5ac4ba7ad31bdca391d098082e64b",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1717602782,
"narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=",
"lastModified": 1744463964,
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6",
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"type": "github"
},
"original": {
@ -67,8 +116,10 @@
},
"root": {
"inputs": {
"hyprgraphics": "hyprgraphics",
"hyprlang": "hyprlang",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"nixpkgs": "nixpkgs",
"systems": "systems"
}

View file

@ -5,6 +5,13 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
hyprgraphics = {
url = "github:hyprwm/hyprgraphics";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprutils = {
url = "github:hyprwm/hyprutils";
inputs.nixpkgs.follows = "nixpkgs";
@ -17,6 +24,12 @@
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
hyprwayland-scanner = {
url = "github:hyprwm/hyprwayland-scanner";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
};
outputs = {
@ -33,7 +46,7 @@
overlays = with self.overlays; [default];
});
in {
overlays = import ./nix/overlays.nix {inherit inputs lib;};
overlays = import ./nix/overlays.nix {inherit inputs lib self;};
packages = eachSystem (system: {
default = self.packages.${system}.hyprlock;

View file

@ -4,20 +4,23 @@
cmake,
pkg-config,
cairo,
file,
libdrm,
libGL,
libjpeg,
libwebp,
libxkbcommon,
mesa,
libgbm,
hyprgraphics,
hyprlang,
hyprutils,
hyprwayland-scanner,
pam,
pango,
sdbus-cpp,
systemdLibs,
wayland,
wayland-protocols,
wayland-scanner,
version ? "git",
shortRev ? "",
}:
stdenv.mkDerivation {
pname = "hyprlock";
@ -28,25 +31,32 @@ stdenv.mkDerivation {
nativeBuildInputs = [
cmake
pkg-config
hyprwayland-scanner
wayland-scanner
];
buildInputs = [
cairo
file
libdrm
libGL
libjpeg
libwebp
libxkbcommon
mesa
libgbm
hyprgraphics
hyprlang
hyprutils
pam
pango
sdbus-cpp
systemdLibs
wayland
wayland-protocols
];
cmakeFlags = lib.mapAttrsToList lib.cmakeFeature {
HYPRLOCK_COMMIT = shortRev;
HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit)
};
meta = {
homepage = "https://github.com/hyprwm/hyprlock";
description = "A gpu-accelerated screen lock for Hyprland";

View file

@ -1,24 +1,44 @@
{
lib,
inputs,
self,
}: let
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in {
default = inputs.self.overlays.hyprlock;
hyprlock = lib.composeManyExtensions [
inputs.hyprgraphics.overlays.default
inputs.hyprlang.overlays.default
inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default
inputs.self.overlays.sdbuscpp
(final: prev: {
hyprlock = prev.callPackage ./default.nix {
stdenv = prev.gcc13Stdenv;
version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
stdenv = prev.gcc14Stdenv;
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
inherit (final) hyprlang;
shortRev = self.sourceInfo.shortRev or "dirty";
};
})
];
sdbuscpp = final: prev: {
sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: {
version = "2.0.0";
src = final.fetchFromGitHub {
owner = "Kistler-group";
repo = "sdbus-cpp";
rev = "refs/tags/v${self.version}";
hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g=";
};
});
};
}

124
src/auth/Auth.cpp Normal file
View file

@ -0,0 +1,124 @@
#include "Auth.hpp"
#include "Pam.hpp"
#include "Fingerprint.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "src/helpers/Log.hpp"
#include <hyprlang.hpp>
#include <memory>
CAuth::CAuth() {
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
if (*ENABLEPAM)
m_vImpls.emplace_back(makeShared<CPam>());
static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:enabled");
if (*ENABLEFINGERPRINT)
m_vImpls.emplace_back(makeShared<CFingerprint>());
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
}
void CAuth::start() {
for (const auto& i : m_vImpls) {
i->init();
}
}
void CAuth::submitInput(const std::string& input) {
for (const auto& i : m_vImpls) {
i->handleInput(input);
}
g_pHyprlock->clearPasswordBuffer();
}
bool CAuth::checkWaiting() {
return std::ranges::any_of(m_vImpls, [](const auto& i) { return i->checkWaiting(); });
}
const std::string& CAuth::getCurrentFailText() {
return m_sCurrentFail.failText;
}
std::optional<std::string> CAuth::getFailText(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastFailText();
}
return std::nullopt;
}
std::optional<std::string> CAuth::getPrompt(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastPrompt();
}
return std::nullopt;
}
size_t CAuth::getFailedAttempts() {
return m_sCurrentFail.failedAttempts;
}
SP<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i;
}
return nullptr;
}
void CAuth::terminate() {
for (const auto& i : m_vImpls) {
i->terminate();
}
}
static void unlockCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->unlock();
}
void CAuth::enqueueUnlock() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), unlockCallback, nullptr);
}
static void passwordFailCallback(std::shared_ptr<CTimer> self, void* data) {
g_pAuth->m_bDisplayFailText = true;
g_pHyprlock->enqueueForceUpdateTimers();
g_pHyprlock->renderAllOutputs();
}
static void displayFailTimeoutCallback(std::shared_ptr<CTimer> self, void* data) {
if (g_pAuth->m_bDisplayFailText) {
g_pAuth->m_bDisplayFailText = false;
g_pHyprlock->renderAllOutputs();
}
}
void CAuth::enqueueFail(const std::string& failText, eAuthImplementations implType) {
static const auto FAILTIMEOUT = g_pConfigManager->getValue<Hyprlang::INT>("general:fail_timeout");
m_sCurrentFail.failText = failText;
m_sCurrentFail.failSource = implType;
m_sCurrentFail.failedAttempts++;
Debug::log(LOG, "Failed attempts: {}", m_sCurrentFail.failedAttempts);
if (m_resetDisplayFailTimer) {
m_resetDisplayFailTimer->cancel();
m_resetDisplayFailTimer.reset();
}
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordFailCallback, nullptr);
m_resetDisplayFailTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(*FAILTIMEOUT), displayFailTimeoutCallback, nullptr);
}
void CAuth::resetDisplayFail() {
g_pAuth->m_bDisplayFailText = false;
m_resetDisplayFailTimer->cancel();
m_resetDisplayFailTimer.reset();
}

67
src/auth/Auth.hpp Normal file
View file

@ -0,0 +1,67 @@
#pragma once
#include <optional>
#include <vector>
#include "../defines.hpp"
#include "../core/Timer.hpp"
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
};
class IAuthImplementation {
public:
virtual ~IAuthImplementation() = default;
virtual eAuthImplementations getImplType() = 0;
virtual void init() = 0;
virtual void handleInput(const std::string& input) = 0;
virtual bool checkWaiting() = 0;
virtual std::optional<std::string> getLastFailText() = 0;
virtual std::optional<std::string> getLastPrompt() = 0;
virtual void terminate() = 0;
friend class CAuth;
};
class CAuth {
public:
CAuth();
void start();
void submitInput(const std::string& input);
bool checkWaiting();
const std::string& getCurrentFailText();
std::optional<std::string> getFailText(eAuthImplementations implType);
std::optional<std::string> getPrompt(eAuthImplementations implType);
size_t getFailedAttempts();
SP<IAuthImplementation> getImpl(eAuthImplementations implType);
void terminate();
void enqueueUnlock();
void enqueueFail(const std::string& failText, eAuthImplementations implType);
void resetDisplayFail();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
private:
struct {
std::string failText = "";
eAuthImplementations failSource = AUTH_IMPL_PAM;
size_t failedAttempts = 0;
} m_sCurrentFail;
std::vector<SP<IAuthImplementation>> m_vImpls;
std::shared_ptr<CTimer> m_resetDisplayFailTimer;
};
inline UP<CAuth> g_pAuth;

270
src/auth/Fingerprint.cpp Normal file
View file

@ -0,0 +1,270 @@
#include "Fingerprint.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include <memory>
#include <unistd.h>
#include <pwd.h>
#include <cstring>
static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"};
static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"};
static const auto MANAGER = sdbus::ServiceName{"net.reactivated.Fprint.Manager"};
static const auto LOGIN_MANAGER = sdbus::ServiceName{"org.freedesktop.login1.Manager"};
enum MatchResult {
MATCH_INVALID = 0,
MATCH_NO_MATCH,
MATCH_MATCHED,
MATCH_RETRY,
MATCH_SWIPE_TOO_SHORT,
MATCH_FINGER_NOT_CENTERED,
MATCH_REMOVE_AND_RETRY,
MATCH_DISCONNECTED,
MATCH_UNKNOWN_ERROR,
};
static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MATCH_NO_MATCH},
{"verify-match", MATCH_MATCHED},
{"verify-retry-scan", MATCH_RETRY},
{"verify-swipe-too-short", MATCH_SWIPE_TOO_SHORT},
{"verify-finger-not-centered", MATCH_FINGER_NOT_CENTERED},
{"verify-remove-and-retry", MATCH_REMOVE_AND_RETRY},
{"verify-disconnected", MATCH_DISCONNECTED},
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
CFingerprint::CFingerprint() {
static const auto FINGERPRINTREADY = g_pConfigManager->getValue<Hyprlang::STRING>("auth:fingerprint:ready_message");
m_sFingerprintReady = *FINGERPRINTREADY;
static const auto FINGERPRINTPRESENT = g_pConfigManager->getValue<Hyprlang::STRING>("auth:fingerprint:present_message");
m_sFingerprintPresent = *FINGERPRINTPRESENT;
}
CFingerprint::~CFingerprint() {
;
}
void CFingerprint::init() {
try {
m_sDBUSState.connection = sdbus::createSystemBusConnection();
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
} catch (sdbus::Error& e) {
Debug::log(ERR, "fprint: Failed to setup dbus ({})", e.what());
m_sDBUSState.connection.reset();
return;
}
m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::Variant preparingForSleep) {
if (e) {
Debug::log(WARN, "fprint: Failed getting value for PreparingForSleep: {}", e->what());
return;
}
m_sDBUSState.sleeping = preparingForSleep.get<bool>();
// When entering sleep, the wake signal will trigger startVerify().
if (m_sDBUSState.sleeping)
return;
startVerify();
});
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
m_sDBUSState.sleeping = start;
if (!m_sDBUSState.sleeping && !m_sDBUSState.verifying)
startVerify();
});
}
void CFingerprint::handleInput(const std::string& input) {
;
}
std::optional<std::string> CFingerprint::getLastFailText() {
if (!m_sFailureReason.empty())
return std::optional(m_sFailureReason);
return std::nullopt;
}
std::optional<std::string> CFingerprint::getLastPrompt() {
if (!m_sPrompt.empty())
return std::optional(m_sPrompt);
return std::nullopt;
}
bool CFingerprint::checkWaiting() {
return false;
}
void CFingerprint::terminate() {
if (!m_sDBUSState.abort)
releaseDevice();
}
std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
bool CFingerprint::createDeviceProxy() {
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
sdbus::ObjectPath path;
try {
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: couldn't connect to Fprint service ({})", e.what());
return false;
}
Debug::log(LOG, "fprint: using device path {}", path.c_str());
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, path);
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "fprint: finger selected: {}", finger); });
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); });
m_sDBUSState.device->uponSignal("PropertiesChanged")
.onInterface("org.freedesktop.DBus.Properties")
.call([this](const std::string& interface, const std::map<std::string, sdbus::Variant>& properties) {
if (interface != DEVICE || m_sDBUSState.done)
return;
try {
const auto presentVariant = properties.at("finger-present");
bool isPresent = presentVariant.get<bool>();
if (!isPresent)
return;
m_sPrompt = m_sFingerprintPresent;
g_pHyprlock->enqueueForceUpdateTimers();
} catch (std::out_of_range& e) {}
});
return true;
}
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
Debug::log(LOG, "fprint: handling status {}", result);
auto matchResult = s_mapStringToTestType[result];
bool authenticated = false;
bool retry = false;
if (m_sDBUSState.sleeping) {
stopVerify();
Debug::log(LOG, "fprint: device suspended");
return;
}
switch (matchResult) {
case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break;
case MATCH_NO_MATCH:
stopVerify();
if (m_sDBUSState.retries >= 3) {
m_sFailureReason = "Fingerprint auth disabled (too many failed attempts)";
} else {
done = false;
static const auto RETRYDELAY = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:retry_delay");
g_pHyprlock->addTimer(std::chrono::milliseconds(*RETRYDELAY), [](std::shared_ptr<CTimer> self, void* data) { ((CFingerprint*)data)->startVerify(true); }, this);
m_sFailureReason = "Fingerprint did not match";
}
break;
case MATCH_UNKNOWN_ERROR:
stopVerify();
m_sFailureReason = "Fingerprint auth disabled (unknown error)";
break;
case MATCH_MATCHED:
stopVerify();
authenticated = true;
g_pAuth->enqueueUnlock();
break;
case MATCH_RETRY:
retry = true;
m_sPrompt = "Please retry fingerprint scan";
break;
case MATCH_SWIPE_TOO_SHORT:
retry = true;
m_sPrompt = "Swipe too short - try again";
break;
case MATCH_FINGER_NOT_CENTERED:
retry = true;
m_sPrompt = "Finger not centered - try again";
break;
case MATCH_REMOVE_AND_RETRY:
retry = true;
m_sPrompt = "Remove your finger and try again";
break;
case MATCH_DISCONNECTED:
m_sFailureReason = "Fingerprint device disconnected";
m_sDBUSState.abort = true;
break;
}
if (!authenticated && !retry)
g_pAuth->enqueueFail(m_sFailureReason, AUTH_IMPL_FINGERPRINT);
else if (retry)
g_pHyprlock->enqueueForceUpdateTimers();
if (done || m_sDBUSState.abort)
m_sDBUSState.done = true;
}
void CFingerprint::claimDevice() {
const auto currentUser = ""; // Empty string means use the caller's id.
m_sDBUSState.device->callMethodAsync("Claim").onInterface(DEVICE).withArguments(currentUser).uponReplyInvoke([this](std::optional<sdbus::Error> e) {
if (e)
Debug::log(WARN, "fprint: could not claim device, {}", e->what());
else {
Debug::log(LOG, "fprint: claimed device");
startVerify();
}
});
}
void CFingerprint::startVerify(bool isRetry) {
m_sDBUSState.verifying = true;
if (!m_sDBUSState.device) {
if (!createDeviceProxy())
return;
claimDevice();
return;
}
auto finger = "any"; // Any finger.
m_sDBUSState.device->callMethodAsync("VerifyStart").onInterface(DEVICE).withArguments(finger).uponReplyInvoke([this, isRetry](std::optional<sdbus::Error> e) {
if (e) {
Debug::log(WARN, "fprint: could not start verifying, {}", e->what());
if (isRetry)
m_sFailureReason = "Fingerprint auth disabled (failed to restart)";
} else {
Debug::log(LOG, "fprint: started verifying");
if (isRetry) {
m_sDBUSState.retries++;
m_sPrompt = "Could not match fingerprint. Try again.";
} else
m_sPrompt = m_sFingerprintReady;
}
g_pHyprlock->enqueueForceUpdateTimers();
});
}
bool CFingerprint::stopVerify() {
m_sDBUSState.verifying = false;
if (!m_sDBUSState.device)
return false;
try {
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: could not stop verifying, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: stopped verification");
return true;
}
bool CFingerprint::releaseDevice() {
if (!m_sDBUSState.device)
return false;
try {
m_sDBUSState.device->callMethod("Release").onInterface(DEVICE);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: could not release device, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: released device");
return true;
}

53
src/auth/Fingerprint.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include "Auth.hpp"
#include <memory>
#include <optional>
#include <string>
#include <sdbus-c++/sdbus-c++.h>
class CFingerprint : public IAuthImplementation {
public:
CFingerprint();
virtual ~CFingerprint();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_FINGERPRINT;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
std::shared_ptr<sdbus::IConnection> getConnection();
private:
struct SDBUSState {
std::shared_ptr<sdbus::IConnection> connection;
std::unique_ptr<sdbus::IProxy> login;
std::unique_ptr<sdbus::IProxy> device;
bool abort = false;
bool done = false;
int retries = 0;
bool sleeping = false;
bool verifying = false;
} m_sDBUSState;
std::string m_sFingerprintReady;
std::string m_sFingerprintPresent;
std::string m_sPrompt{""};
std::string m_sFailureReason{""};
void handleVerifyStatus(const std::string& result, const bool done);
bool createDeviceProxy();
void claimDevice();
void startVerify(bool isRetry = false);
bool stopVerify();
bool releaseDevice();
};

View file

@ -1,7 +1,7 @@
#include "Auth.hpp"
#include "hyprlock.hpp"
#include "Pam.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "src/config/ConfigManager.hpp"
#include "../config/ConfigManager.hpp"
#include <filesystem>
#include <unistd.h>
@ -15,7 +15,7 @@
#include <thread>
int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
const auto CONVERSATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr;
const auto CONVERSATIONSTATE = (CPam::SPamConversationState*)appdata_ptr;
struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response));
bool initialPrompt = true;
@ -32,20 +32,27 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
// Some pam configurations ask for the password twice for whatever reason (Fedora su for example)
// When the prompt is the same as the last one, I guess our answer can be the same.
if (initialPrompt || PROMPTCHANGED) {
if (!initialPrompt && PROMPTCHANGED) {
CONVERSATIONSTATE->prompt = PROMPT;
g_pAuth->waitForInput();
CONVERSATIONSTATE->waitForInput();
}
// Needed for unlocks via SIGUSR1
if (g_pHyprlock->m_bTerminate)
if (g_pHyprlock->isUnlocked())
return PAM_CONV_ERR;
pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str());
initialPrompt = false;
} break;
case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break;
case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break;
case PAM_TEXT_INFO:
Debug::log(LOG, "PAM: {}", msg[i]->msg);
// Targets this log from pam_faillock: https://github.com/linux-pam/linux-pam/blob/fa3295e079dbbc241906f29bde5fb71bc4172771/modules/pam_faillock/pam_faillock.c#L417
if (const auto MSG = std::string(msg[i]->msg); MSG.contains("left to unlock")) {
CONVERSATIONSTATE->failText = MSG;
CONVERSATIONSTATE->failTextFromPam = true;
}
break;
}
}
@ -53,38 +60,60 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
return PAM_SUCCESS;
}
CAuth::CAuth() {
static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module"));
m_sPamModule = *PPAMMODULE;
CPam::CPam() {
static const auto PAMMODULE = g_pConfigManager->getValue<Hyprlang::STRING>("auth:pam:module");
m_sPamModule = *PAMMODULE;
if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) {
Debug::log(ERR, "Pam module \"/etc/pam.d/{}\" does not exist! Falling back to \"/etc/pam.d/su\"", m_sPamModule);
Debug::log(ERR, R"(Pam module "/etc/pam.d/{}" does not exist! Falling back to "/etc/pam.d/su")", m_sPamModule);
m_sPamModule = "su";
}
m_sConversationState.waitForInput = [this]() { this->waitForInput(); };
}
static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->onPasswordCheckTimer();
CPam::~CPam() {
;
}
void CAuth::start() {
std::thread([this]() {
resetConversation();
auth();
void CPam::init() {
m_thread = std::thread([this]() {
while (true) {
resetConversation();
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
}).detach();
// Initial input
m_sConversationState.prompt = "Password: ";
waitForInput();
// For grace or SIGUSR1 unlocks
if (g_pHyprlock->isUnlocked())
return;
const auto AUTHENTICATED = auth();
// For SIGUSR1 unlocks
if (g_pHyprlock->isUnlocked())
return;
if (!AUTHENTICATED)
g_pAuth->enqueueFail(m_sConversationState.failText, AUTH_IMPL_PAM);
else {
g_pAuth->enqueueUnlock();
return;
}
}
});
}
bool CAuth::auth() {
const pam_conv localConv = {conv, (void*)&m_sConversationState};
pam_handle_t* handle = NULL;
bool CPam::auth() {
const pam_conv localConv = {.conv = conv, .appdata_ptr = (void*)&m_sConversationState};
pam_handle_t* handle = nullptr;
auto uidPassword = getpwuid(getuid());
RASSERT(uidPassword && uidPassword->pw_name, "Failed to get username (getpwuid)");
int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle);
int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle);
if (ret != PAM_SUCCESS) {
m_sConversationState.success = false;
m_sConversationState.failText = "pam_start failed";
Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule);
return false;
@ -97,31 +126,19 @@ bool CAuth::auth() {
m_sConversationState.waitingForPamAuth = false;
if (ret != PAM_SUCCESS) {
m_sConversationState.success = false;
m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed";
if (!m_sConversationState.failTextFromPam)
m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed";
Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule);
return false;
}
m_sConversationState.success = true;
m_sConversationState.failText = "Successfully authenticated";
Debug::log(LOG, "auth: authenticated for {}", m_sPamModule);
return true;
}
bool CAuth::didAuthSucceed() {
return m_sConversationState.success;
}
// clearing the input must be done from the main thread
static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->clearPasswordBuffer();
}
void CAuth::waitForInput() {
g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr);
void CPam::waitForInput() {
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
m_bBlockInput = false;
m_sConversationState.waitingForPamAuth = false;
@ -130,7 +147,7 @@ void CAuth::waitForInput() {
m_bBlockInput = true;
}
void CAuth::submitInput(std::string input) {
void CPam::handleInput(const std::string& input) {
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
if (!m_sConversationState.inputRequested)
@ -142,25 +159,27 @@ void CAuth::submitInput(std::string input) {
m_sConversationState.inputSubmittedCondition.notify_all();
}
std::optional<std::string> CAuth::getLastFailText() {
std::optional<std::string> CPam::getLastFailText() {
return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText);
}
std::optional<std::string> CAuth::getLastPrompt() {
std::optional<std::string> CPam::getLastPrompt() {
return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt);
}
bool CAuth::checkWaiting() {
bool CPam::checkWaiting() {
return m_bBlockInput || m_sConversationState.waitingForPamAuth;
}
void CAuth::terminate() {
void CPam::terminate() {
m_sConversationState.inputSubmittedCondition.notify_all();
if (m_thread.joinable())
m_thread.join();
}
void CAuth::resetConversation() {
void CPam::resetConversation() {
m_sConversationState.input = "";
m_sConversationState.waitingForPamAuth = false;
m_sConversationState.inputRequested = false;
m_sConversationState.success = false;
m_sConversationState.failTextFromPam = false;
}

53
src/auth/Pam.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include "Auth.hpp"
#include <optional>
#include <string>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <thread>
class CPam : public IAuthImplementation {
public:
struct SPamConversationState {
std::string input = "";
std::string prompt = "";
std::string failText = "";
std::mutex inputMutex;
std::condition_variable inputSubmittedCondition;
bool waitingForPamAuth = false;
bool inputRequested = false;
bool failTextFromPam = false;
std::function<void()> waitForInput = []() {};
};
CPam();
void waitForInput();
virtual ~CPam();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_PAM;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
private:
std::thread m_thread;
SPamConversationState m_sConversationState;
bool m_bBlockInput = true;
std::string m_sPamModule;
bool auth();
void resetConversation();
};

View file

@ -0,0 +1,133 @@
#pragma once
#include "../helpers/Log.hpp"
#include "../helpers/Color.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/VarList.hpp>
#include <any>
#include <string>
#include <vector>
#include <cmath>
using namespace Hyprutils::String;
enum eConfigValueDataTypes {
CVD_TYPE_INVALID = -1,
CVD_TYPE_LAYOUT = 0,
CVD_TYPE_GRADIENT = 1,
};
class ICustomConfigValueData {
public:
virtual ~ICustomConfigValueData() = 0;
virtual eConfigValueDataTypes getDataType() = 0;
virtual std::string toString() = 0;
};
class CLayoutValueData : public ICustomConfigValueData {
public:
CLayoutValueData() = default;
virtual ~CLayoutValueData() {};
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_LAYOUT;
}
virtual std::string toString() {
return std::format("{}{},{}{}", m_vValues.x, (m_sIsRelative.x) ? "%" : "px", m_vValues.y, (m_sIsRelative.y) ? "%" : "px");
}
static CLayoutValueData* fromAnyPv(const std::any& v) {
RASSERT(v.type() == typeid(void*), "Invalid config value type");
const auto P = (CLayoutValueData*)std::any_cast<void*>(v);
RASSERT(P, "Empty config value");
return P;
}
Hyprutils::Math::Vector2D getAbsolute(const Hyprutils::Math::Vector2D& viewport) {
return {
(m_sIsRelative.x ? (m_vValues.x / 100) * viewport.x : m_vValues.x),
(m_sIsRelative.y ? (m_vValues.y / 100) * viewport.y : m_vValues.y),
};
}
Hyprutils::Math::Vector2D m_vValues;
struct {
bool x = false;
bool y = false;
} m_sIsRelative;
};
class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData() {};
CGradientValueData(CHyprColor col) {
m_vColors.push_back(col);
updateColorsOk();
};
virtual ~CGradientValueData() {};
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_GRADIENT;
}
void reset(CHyprColor col) {
m_vColors.clear();
m_vColors.emplace_back(col);
m_fAngle = 0;
updateColorsOk();
}
void updateColorsOk() {
m_vColorsOkLabA.clear();
for (auto& c : m_vColors) {
const auto OKLAB = c.asOkLab();
m_vColorsOkLabA.emplace_back(OKLAB.l);
m_vColorsOkLabA.emplace_back(OKLAB.a);
m_vColorsOkLabA.emplace_back(OKLAB.b);
m_vColorsOkLabA.emplace_back(c.a);
}
}
/* Vector containing the colors */
std::vector<CHyprColor> m_vColors;
/* Vector containing pure colors for shoving into opengl */
std::vector<float> m_vColorsOkLabA;
/* Float corresponding to the angle (rad) */
float m_fAngle = 0;
/* Whether this gradient stores a fallback value (not exlicitly set) */
bool m_bIsFallback = false;
//
bool operator==(const CGradientValueData& other) const {
if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle)
return false;
for (size_t i = 0; i < m_vColors.size(); ++i)
if (m_vColors[i] != other.m_vColors[i])
return false;
return true;
}
virtual std::string toString() {
std::string result;
for (auto& c : m_vColors) {
result += std::format("{:x} ", c.getAsHex());
}
result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI));
return result;
}
static CGradientValueData* fromAnyPv(const std::any& v) {
RASSERT(v.type() == typeid(void*), "Invalid config value type");
const auto P = (CGradientValueData*)std::any_cast<void*>(v);
RASSERT(P, "Empty config value");
return P;
}
};

View file

@ -1,10 +1,23 @@
#include "ConfigManager.hpp"
#include "ConfigDataValues.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../core/AnimationManager.hpp"
#include <hyprlang.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/path/Path.hpp>
#include <filesystem>
#include <glob.h>
#include <cstring>
#include <mutex>
using namespace Hyprutils::String;
using namespace Hyprutils::Animation;
ICustomConfigValueData::~ICustomConfigValueData() {
; // empty
}
static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
@ -17,22 +30,163 @@ static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
return result;
}
static std::string getConfigDir() {
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
static Hyprlang::CParseResult handleBezier(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
if (xdgConfigHome && std::filesystem::path(xdgConfigHome).is_absolute())
return xdgConfigHome;
const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);
static const char* home = getenv("HOME");
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
if (!home)
throw std::runtime_error("Neither HOME nor XDG_CONFIG_HOME is set in the environment. Cannot determine config directory.");
static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
return home + std::string("/.config");
const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult configHandleLayoutOption(const char* v, void** data) {
const std::string VALUE = v;
Hyprlang::CParseResult result;
if (!*data)
*data = new CLayoutValueData();
const auto DATA = (CLayoutValueData*)(*data);
const auto SPLIT = VALUE.find(',');
if (SPLIT == std::string::npos) {
result.setError(std::format("expected two comma seperated values, got {}", VALUE).c_str());
return result;
}
auto lhs = VALUE.substr(0, SPLIT);
auto rhs = VALUE.substr(SPLIT + 1);
if (rhs.starts_with(" "))
rhs = rhs.substr(1);
if (lhs.contains(",") || rhs.contains(",")) {
result.setError(std::format("too many arguments in {}", VALUE).c_str());
return result;
}
if (lhs.ends_with("%")) {
DATA->m_sIsRelative.x = true;
lhs.pop_back();
}
if (rhs.ends_with("%")) {
DATA->m_sIsRelative.y = true;
rhs.pop_back();
}
DATA->m_vValues = Hyprutils::Math::Vector2D{std::stof(lhs), std::stof(rhs)};
return result;
}
static void configHandleLayoutOptionDestroy(void** data) {
if (*data)
delete reinterpret_cast<CLayoutValueData*>(*data);
}
static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) {
const std::string V = VALUE;
if (!*data)
*data = new CGradientValueData();
const auto DATA = reinterpret_cast<CGradientValueData*>(*data);
DATA->m_vColors.clear();
DATA->m_bIsFallback = false;
std::string parseError = "";
std::string rolling = V;
while (!rolling.empty()) {
const auto SPACEPOS = rolling.find(' ');
const bool LAST = SPACEPOS == std::string::npos;
std::string var = rolling.substr(0, SPACEPOS);
if (var.find("rgb") != std::string::npos) { // rgb(a)
const auto CLOSEPARENPOS = rolling.find(')');
if (CLOSEPARENPOS == std::string::npos || CLOSEPARENPOS + 1 >= rolling.length()) {
var = trim(rolling);
rolling.clear();
} else {
var = rolling.substr(0, CLOSEPARENPOS + 1);
rolling = trim(rolling.substr(CLOSEPARENPOS + 2));
}
} else if (var.find("deg") != std::string::npos) { // last arg
try {
DATA->m_fAngle = std::stoi(var.substr(0, var.find("deg"))) * (M_PI / 180.0); // radians
} catch (...) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V;
}
break;
} else // hex
rolling = trim(rolling.substr(LAST ? rolling.length() : SPACEPOS + 1));
if (DATA->m_vColors.size() >= 10) {
Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V);
parseError = "Error parsing gradient " + V + ": max colors is 10.";
break;
}
if (var.empty())
continue;
try {
DATA->m_vColors.emplace_back(configStringToInt(var));
} catch (std::exception& e) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V + ": " + e.what();
}
}
if (V.empty()) {
DATA->m_bIsFallback = true;
DATA->m_vColors.emplace_back(0); // transparent
}
if (DATA->m_vColors.size() == 0) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V + ": No colors?";
DATA->m_vColors.emplace_back(0); // transparent
}
DATA->updateColorsOk();
Hyprlang::CParseResult result;
if (!parseError.empty())
result.setError(parseError.c_str());
return result;
}
static void configHandleGradientDestroy(void** data) {
if (*data)
delete reinterpret_cast<CGradientValueData*>(*data);
}
static std::string getMainConfigPath() {
return getConfigDir() + "/hypr/hyprlock.conf";
static const auto paths = Hyprutils::Path::findConfig("hyprlock");
if (paths.first.has_value())
return paths.first.value();
else
throw std::runtime_error("Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr.");
}
CConfigManager::CConfigManager(std::string configPath) :
@ -40,6 +194,14 @@ CConfigManager::CConfigManager(std::string configPath) :
configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath;
}
inline static constexpr auto GRADIENTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE {
return Hyprlang::CUSTOMTYPE{&configHandleGradientSet, configHandleGradientDestroy, default_value};
};
inline static constexpr auto LAYOUTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE {
return Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, default_value};
};
void CConfigManager::init() {
#define SHADOWABLE(name) \
@ -48,14 +210,25 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0});
#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0});
m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0});
m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0});
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
m_config.addConfigValue("general:screencopy_mode", Hyprlang::INT{0});
m_config.addConfigValue("general:fail_timeout", Hyprlang::INT{2000});
m_config.addConfigValue("auth:pam:enabled", Hyprlang::INT{1});
m_config.addConfigValue("auth:pam:module", Hyprlang::STRING{"hyprlock"});
m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0});
m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"});
m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"});
m_config.addConfigValue("auth:fingerprint:retry_delay", Hyprlang::INT{250});
m_config.addConfigValue("animations:enabled", Hyprlang::INT{1});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
@ -69,21 +242,25 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686});
m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05});
m_config.addSpecialConfigValue("background", "zindex", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("background", "reload_time", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("background", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "crossfade_time", Hyprlang::FLOAT{-1.0});
m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("shape", "size", Hyprlang::VEC2{100, 100});
m_config.addSpecialConfigValue("shape", "size", LAYOUTCONFIG("100,100"));
m_config.addSpecialConfigValue("shape", "rounding", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "border_size", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "border_color", Hyprlang::INT{0xFF00CFE6});
m_config.addSpecialConfigValue("shape", "border_color", GRADIENTCONFIG("0xFF00CFE6"));
m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111});
m_config.addSpecialConfigValue("shape", "position", Hyprlang::VEC2{0, 80});
m_config.addSpecialConfigValue("shape", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("shape", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("shape", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("shape", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
SHADOWABLE("shape");
CLICKABLE("shape");
m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""});
@ -91,8 +268,8 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "size", Hyprlang::INT{150});
m_config.addSpecialConfigValue("image", "rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("image", "border_size", Hyprlang::INT{4});
m_config.addSpecialConfigValue("image", "border_color", Hyprlang::INT{0xFFDDDDDD});
m_config.addSpecialConfigValue("image", "position", Hyprlang::VEC2{0, 200});
m_config.addSpecialConfigValue("image", "border_color", GRADIENTCONFIG("0xFFDDDDDD"));
m_config.addSpecialConfigValue("image", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0});
@ -100,33 +277,36 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0});
SHADOWABLE("image");
CLICKABLE("image");
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "size", Hyprlang::VEC2{400, 90});
m_config.addSpecialConfigValue("input-field", "size", LAYOUTCONFIG("400,90"));
m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD});
m_config.addSpecialConfigValue("input-field", "outer_color", Hyprlang::INT{0xFF111111});
m_config.addSpecialConfigValue("input-field", "outer_color", GRADIENTCONFIG("0xFF111111"));
m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4});
m_config.addSpecialConfigValue("input-field", "dots_size", Hyprlang::FLOAT{0.25});
m_config.addSpecialConfigValue("input-field", "dots_center", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "dots_spacing", Hyprlang::FLOAT{0.2});
m_config.addSpecialConfigValue("input-field", "dots_rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "dots_text_format", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "fade_on_empty", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "fade_timeout", Hyprlang::INT{2000});
m_config.addSpecialConfigValue("input-field", "font_color", Hyprlang::INT{0xFF000000});
m_config.addSpecialConfigValue("input-field", "font_family", Hyprlang::STRING{"Sans"});
m_config.addSpecialConfigValue("input-field", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("input-field", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("input-field", "position", Hyprlang::VEC2{0, -20});
m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"});
m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "hide_input_base_color", Hyprlang::INT{0xEE00FF99});
m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "check_color", Hyprlang::INT{0xFFCC8822});
m_config.addSpecialConfigValue("input-field", "fail_color", Hyprlang::INT{0xFFCC2222});
m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88"));
m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222"));
m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"});
m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300});
m_config.addSpecialConfigValue("input-field", "capslock_color", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "numlock_color", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "bothlock_color", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "invert_numlock", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "swap_font_color", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "zindex", Hyprlang::INT{0});
@ -134,19 +314,45 @@ void CConfigManager::init() {
m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "position", Hyprlang::VEC2{400, 90});
m_config.addSpecialConfigValue("label", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("label", "color", Hyprlang::INT{0xFFFFFFFF});
m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16});
m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"});
m_config.addSpecialConfigValue("label", "font_family", Hyprlang::STRING{"Sans"});
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0});
SHADOWABLE("label");
CLICKABLE("label");
m_config.registerHandler(&::handleSource, "source", {false});
m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
m_config.registerHandler(&::handleAnimation, "animation", {.allowFlags = false});
//
// Init Animations
//
m_AnimationTree.createNode("global");
// toplevel
m_AnimationTree.createNode("fade", "global");
m_AnimationTree.createNode("inputField", "global");
// inputField
m_AnimationTree.createNode("inputFieldColors", "inputField");
m_AnimationTree.createNode("inputFieldFade", "inputField");
m_AnimationTree.createNode("inputFieldWidth", "inputField");
m_AnimationTree.createNode("inputFieldDots", "inputField");
// fade
m_AnimationTree.createNode("fadeIn", "fade");
m_AnimationTree.createNode("fadeOut", "fade");
// set config for root node
m_AnimationTree.setConfigForNode("global", 1, 8.f, "default");
m_AnimationTree.setConfigForNode("inputFieldColors", 1, 8.f, "linear");
m_config.commence();
@ -156,13 +362,7 @@ void CConfigManager::init() {
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
#undef SHADOWABLE
}
std::mutex configMtx;
void* const* CConfigManager::getValuePtr(const std::string& name) {
std::lock_guard<std::mutex> lg(configMtx);
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
#undef CLICKABLE
}
std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
@ -174,14 +374,17 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
"shadow_boost", m_config.getSpecialConfigValue(name, "shadow_boost", k.c_str()) \
}
#define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())}
//
auto keys = m_config.listKeysForSpecialCategory("background");
result.reserve(keys.size());
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"background",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())),
{
.type = "background",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())),
.values = {
{"path", m_config.getSpecialConfigValue("background", "path", k.c_str())},
{"color", m_config.getSpecialConfigValue("background", "color", k.c_str())},
{"blur_size", m_config.getSpecialConfigValue("background", "blur_size", k.c_str())},
@ -192,6 +395,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())},
{"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("background", "zindex", k.c_str())},
{"reload_time", m_config.getSpecialConfigValue("background", "reload_time", k.c_str())},
{"reload_cmd", m_config.getSpecialConfigValue("background", "reload_cmd", k.c_str())},
{"crossfade_time", m_config.getSpecialConfigValue("background", "crossfade_time", k.c_str())},
}
});
// clang-format on
@ -202,9 +408,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"shape",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())),
{
.type = "shape",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("shape", "monitor", k.c_str())),
.values = {
{"size", m_config.getSpecialConfigValue("shape", "size", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("shape", "rounding", k.c_str())},
{"border_size", m_config.getSpecialConfigValue("shape", "border_size", k.c_str())},
@ -217,6 +423,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())},
SHADOWABLE("shape"),
CLICKABLE("shape"),
}
});
// clang-format on
@ -227,9 +434,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"image",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("image", "monitor", k.c_str())),
{
.type = "image",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("image", "monitor", k.c_str())),
.values = {
{"path", m_config.getSpecialConfigValue("image", "path", k.c_str())},
{"size", m_config.getSpecialConfigValue("image", "size", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("image", "rounding", k.c_str())},
@ -243,6 +450,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())},
SHADOWABLE("image"),
CLICKABLE("image"),
}
});
// clang-format on
@ -252,9 +460,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"input-field",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())),
{
.type = "input-field",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())),
.values = {
{"size", m_config.getSpecialConfigValue("input-field", "size", k.c_str())},
{"inner_color", m_config.getSpecialConfigValue("input-field", "inner_color", k.c_str())},
{"outer_color", m_config.getSpecialConfigValue("input-field", "outer_color", k.c_str())},
@ -263,19 +471,21 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"dots_spacing", m_config.getSpecialConfigValue("input-field", "dots_spacing", k.c_str())},
{"dots_center", m_config.getSpecialConfigValue("input-field", "dots_center", k.c_str())},
{"dots_rounding", m_config.getSpecialConfigValue("input-field", "dots_rounding", k.c_str())},
{"dots_text_format", m_config.getSpecialConfigValue("input-field", "dots_text_format", k.c_str())},
{"fade_on_empty", m_config.getSpecialConfigValue("input-field", "fade_on_empty", k.c_str())},
{"fade_timeout", m_config.getSpecialConfigValue("input-field", "fade_timeout", k.c_str())},
{"font_color", m_config.getSpecialConfigValue("input-field", "font_color", k.c_str())},
{"font_family", m_config.getSpecialConfigValue("input-field", "font_family", k.c_str())},
{"halign", m_config.getSpecialConfigValue("input-field", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("input-field", "valign", k.c_str())},
{"position", m_config.getSpecialConfigValue("input-field", "position", k.c_str())},
{"placeholder_text", m_config.getSpecialConfigValue("input-field", "placeholder_text", k.c_str())},
{"hide_input", m_config.getSpecialConfigValue("input-field", "hide_input", k.c_str())},
{"hide_input_base_color", m_config.getSpecialConfigValue("input-field", "hide_input_base_color", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("input-field", "rounding", k.c_str())},
{"check_color", m_config.getSpecialConfigValue("input-field", "check_color", k.c_str())},
{"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())},
{"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())},
{"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", k.c_str())},
{"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())},
{"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())},
{"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())},
@ -292,9 +502,9 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"label",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())),
{
.type = "label",
.monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("label", "monitor", k.c_str())),
.values = {
{"position", m_config.getSpecialConfigValue("label", "position", k.c_str())},
{"color", m_config.getSpecialConfigValue("label", "color", k.c_str())},
{"font_size", m_config.getSpecialConfigValue("label", "font_size", k.c_str())},
@ -306,6 +516,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())},
SHADOWABLE("label"),
CLICKABLE("label"),
}
});
// clang-format on
@ -359,3 +570,80 @@ std::optional<std::string> CConfigManager::handleSource(const std::string& comma
return {};
}
std::optional<std::string> CConfigManager::handleBezier(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
std::string bezierName = ARGS[0];
if (ARGS[1] == "")
return "too few arguments";
float p1x = std::stof(ARGS[1]);
if (ARGS[2] == "")
return "too few arguments";
float p1y = std::stof(ARGS[2]);
if (ARGS[3] == "")
return "too few arguments";
float p2x = std::stof(ARGS[3]);
if (ARGS[4] == "")
return "too few arguments";
float p2y = std::stof(ARGS[4]);
if (ARGS[5] != "")
return "too many arguments";
g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y));
return {};
}
std::optional<std::string> CConfigManager::handleAnimation(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
const auto ANIMNAME = ARGS[0];
if (!m_AnimationTree.nodeExists(ANIMNAME))
return "no such animation";
// This helper casts strings like "1", "true", "off", "yes"... to int.
int64_t enabledInt = configStringToInt(ARGS[1]);
// Checking that the int is 1 or 0 because the helper can return integers out of range.
if (enabledInt > 1 || enabledInt < 0)
return "invalid animation on/off state";
if (!enabledInt) {
m_AnimationTree.setConfigForNode(ANIMNAME, 0, 1, "default");
return {};
}
int64_t speed = -1;
// speed
if (isNumber(ARGS[2], true)) {
speed = std::stof(ARGS[2]);
if (speed <= 0) {
speed = 1.f;
return "invalid speed";
}
} else {
speed = 10.f;
return "invalid speed";
}
std::string bezierName = ARGS[3];
// ARGS[4] (style) currently usused by hyprlock
m_AnimationTree.setConfigForNode(ANIMNAME, enabledInt, speed, bezierName, "");
if (!g_pAnimationManager->bezierExists(bezierName)) {
const auto PANIMNODE = m_AnimationTree.getConfig(ANIMNAME);
PANIMNODE->internalBezier = "default";
return "no such bezier";
}
return {};
}

View file

@ -1,18 +1,23 @@
#pragma once
#include "../helpers/Log.hpp"
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprlang.hpp>
#include <optional>
#include <vector>
#include <memory>
#include <unordered_map>
#include "../defines.hpp"
class CConfigManager {
public:
CConfigManager(std::string configPath);
void init();
void* const* getValuePtr(const std::string& name);
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;
@ -21,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;

View file

@ -0,0 +1,127 @@
#include "AnimationManager.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../config/ConfigDataValues.hpp"
#include "../config/ConfigManager.hpp"
CHyprlockAnimationManager::CHyprlockAnimationManager() {
addBezierWithName("linear", {0, 0}, {1, 1});
}
template <Animable VarType>
void updateVariable(CAnimatedVariable<VarType>& av, const float POINTY, bool warp = false) {
if (warp || !av.enabled() || av.value() == av.goal()) {
av.warp(true, false);
return;
}
const auto DELTA = av.goal() - av.begun();
av.value() = av.begun() + DELTA * POINTY;
}
void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp = false) {
if (warp || !av.enabled() || av.value() == av.goal()) {
av.warp(true, false);
return;
}
// convert both to OkLab, then lerp that, and convert back.
// This is not as fast as just lerping rgb, but it's WAY more precise...
// Use the CHyprColor cache for OkLab
const auto& L1 = av.begun().asOkLab();
const auto& L2 = av.goal().asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)};
}
void updateGradientVariable(CAnimatedVariable<CGradientValueData>& av, const float POINTY, bool warp = false) {
if (warp || av.value() == av.goal()) {
av.warp(true, false);
return;
}
av.value().m_vColors.resize(av.goal().m_vColors.size(), av.goal().m_vColors.back());
for (size_t i = 0; i < av.value().m_vColors.size(); ++i) {
const CHyprColor& sourceCol = (i < av.begun().m_vColors.size()) ? av.begun().m_vColors[i] : av.begun().m_vColors.back();
const CHyprColor& targetCol = (i < av.goal().m_vColors.size()) ? av.goal().m_vColors[i] : av.goal().m_vColors.back();
const auto& L1 = sourceCol.asOkLab();
const auto& L2 = targetCol.asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + ((two - one) * progress); };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value().m_vColors[i] = {lerped, lerp(sourceCol.a, targetCol.a, POINTY)};
}
if (av.begun().m_fAngle != av.goal().m_fAngle) {
const float DELTA = av.goal().m_fAngle - av.begun().m_fAngle;
av.value().m_fAngle = av.begun().m_fAngle + DELTA * POINTY;
}
av.value().updateColorsOk();
}
void CHyprlockAnimationManager::tick() {
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
if (!PAV || !PAV->ok())
continue;
const auto SPENT = PAV->getPercent();
const auto PBEZIER = getBezier(PAV->getBezierName());
const auto POINTY = PBEZIER->getYForPoint(SPENT);
const bool WARP = !*ANIMATIONSENABLED || SPENT >= 1.f;
switch (PAV->m_Type) {
case AVARTYPE_FLOAT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<float>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated float");
updateVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_VECTOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<Vector2D>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated Vector2D");
updateVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_COLOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CHyprColor>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CHyprColor");
updateColorVariable(*pTypedAV, POINTY, WARP);
} break;
case AVARTYPE_GRADIENT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CGradientValueData>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CGradientValueData");
updateGradientVariable(*pTypedAV, POINTY, WARP);
} break;
default: continue;
}
PAV->onUpdate();
}
tickDone();
}
void CHyprlockAnimationManager::scheduleTick() {
m_bTickScheduled = true;
}
void CHyprlockAnimationManager::onTicked() {
m_bTickScheduled = false;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "../helpers/AnimatedVariable.hpp"
#include "../defines.hpp"
class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager {
public:
CHyprlockAnimationManager();
void tick();
virtual void scheduleTick();
virtual void onTicked();
using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig;
template <Animable VarType>
void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig) {
constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType<VarType>;
const auto PAV = makeShared<CAnimatedVariable<VarType>>();
PAV->create(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), PAV, v);
PAV->setConfig(pConfig);
pav = std::move(PAV);
}
bool m_bTickScheduled = false;
};
inline UP<CHyprlockAnimationManager> g_pAnimationManager;

View file

@ -1,54 +0,0 @@
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <mutex>
#include <condition_variable>
class CAuth {
public:
struct SPamConversationState {
std::string input = "";
std::string prompt = "";
std::string failText = "";
std::mutex inputMutex;
std::condition_variable inputSubmittedCondition;
bool waitingForPamAuth = false;
bool inputRequested = false;
bool success = false;
};
CAuth();
void start();
bool auth();
bool didAuthSucceed();
void waitForInput();
void submitInput(std::string input);
std::optional<std::string> getLastFailText();
std::optional<std::string> getLastPrompt();
bool checkWaiting();
void terminate();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
private:
SPamConversationState m_sConversationState;
bool m_bBlockInput = true;
std::string m_sPamModule;
void resetConversation();
};
inline std::unique_ptr<CAuth> g_pAuth;

View file

@ -1,20 +1,23 @@
#include "CursorShape.hpp"
#include "hyprlock.hpp"
#include "Seat.hpp"
CCursorShape::CCursorShape(wp_cursor_shape_manager_v1* mgr) : mgr(mgr) {
if (!g_pHyprlock->m_pPointer)
CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
if (!g_pSeatManager->m_pPointer)
return;
dev = wp_cursor_shape_manager_v1_get_pointer(mgr, g_pHyprlock->m_pPointer);
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
}
void CCursorShape::setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape) {
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
if (!g_pSeatManager->m_pPointer)
return;
if (!dev)
return;
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
wp_cursor_shape_device_v1_set_shape(dev, serial, shape);
dev->sendSetShape(lastCursorSerial, shape);
}
void CCursorShape::hideCursor(const uint32_t serial) {
wl_pointer_set_cursor(g_pHyprlock->m_pPointer, serial, nullptr, 0, 0);
void CCursorShape::hideCursor() {
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
}

View file

@ -1,16 +1,18 @@
#pragma once
#include <wayland-client.h>
#include "cursor-shape-v1-protocol.h"
#include "../defines.hpp"
#include "cursor-shape-v1.hpp"
class CCursorShape {
public:
CCursorShape(wp_cursor_shape_manager_v1* mgr);
CCursorShape(SP<CCWpCursorShapeManagerV1> mgr);
void setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape);
void hideCursor(const uint32_t serial);
void setShape(const wpCursorShapeDeviceV1Shape shape);
void hideCursor();
uint32_t lastCursorSerial = 0;
private:
wp_cursor_shape_manager_v1* mgr = nullptr;
wp_cursor_shape_device_v1* dev = nullptr;
SP<CCWpCursorShapeManagerV1> mgr = nullptr;
SP<CCWpCursorShapeDeviceV1> dev = nullptr;
};

View file

@ -31,21 +31,21 @@ CEGL::CEGL(wl_display* display) {
throw std::runtime_error("EGL_EXT_platform_wayland not supported");
eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if (eglGetPlatformDisplayEXT == NULL)
if (eglGetPlatformDisplayEXT == nullptr)
throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT");
eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
if (eglCreatePlatformWindowSurfaceEXT == NULL)
if (eglCreatePlatformWindowSurfaceEXT == nullptr)
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, NULL);
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr);
EGLint matched = 0;
if (eglDisplay == EGL_NO_DISPLAY) {
Debug::log(CRIT, "Failed to create EGL display");
goto error;
}
if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE) {
if (eglInitialize(eglDisplay, nullptr, nullptr) == EGL_FALSE) {
Debug::log(CRIT, "Failed to initialize EGL");
goto error;
}

View file

@ -1,11 +1,10 @@
#pragma once
#include <wayland-client.h>
#include <memory>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "../defines.hpp"
class CEGL {
public:
CEGL(wl_display*);
@ -20,4 +19,4 @@ class CEGL {
void makeCurrent(EGLSurface surf);
};
inline std::unique_ptr<CEGL> g_pEGL;
inline UP<CEGL> g_pEGL;

View file

@ -1,107 +1,95 @@
#include "LockSurface.hpp"
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../renderer/Renderer.hpp"
static void handleConfigure(void* data, ext_session_lock_surface_v1* surf, uint32_t serial, uint32_t width, uint32_t height) {
const auto PSURF = (CSessionLockSurface*)data;
PSURF->configure({width, height}, serial);
}
static const ext_session_lock_surface_v1_listener lockListener = {
.configure = handleConfigure,
};
static void handlePreferredScale(void* data, wp_fractional_scale_v1* wp_fractional_scale_v1, uint32_t scale) {
const auto PSURF = (CSessionLockSurface*)data;
PSURF->fractionalScale = scale / 120.0;
Debug::log(LOG, "got fractional {}", PSURF->fractionalScale);
if (PSURF->readyForFrame)
PSURF->onScaleUpdate();
}
static const wp_fractional_scale_v1_listener fsListener = {
.preferred_scale = handlePreferredScale,
};
CSessionLockSurface::~CSessionLockSurface() {
if (fractional) {
wp_viewport_destroy(viewport);
wp_fractional_scale_v1_destroy(fractional);
}
if (eglWindow)
wl_egl_window_destroy(eglWindow);
ext_session_lock_surface_v1_destroy(lockSurface);
wl_surface_destroy(surface);
if (frameCallback)
wl_callback_destroy(frameCallback);
}
CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
surface = wl_compositor_create_surface(g_pHyprlock->getCompositor());
CSessionLockSurface::CSessionLockSurface(const SP<COutput>& pOutput) : m_outputRef(pOutput), m_outputID(pOutput->m_ID) {
surface = makeShared<CCWlSurface>(g_pHyprlock->getCompositor()->sendCreateSurface());
RASSERT(surface, "Couldn't create wl_surface");
if (!surface) {
Debug::log(CRIT, "Couldn't create wl_surface");
exit(1);
static const auto FRACTIONALSCALING = g_pConfigManager->getValue<Hyprlang::INT>("general:fractional_scaling");
const auto ENABLE_FSV1 = *FRACTIONALSCALING == 1 || /* auto enable */ (*FRACTIONALSCALING == 2);
const auto PFRACTIONALMGR = g_pHyprlock->getFractionalMgr();
const auto PVIEWPORTER = g_pHyprlock->getViewporter();
if (ENABLE_FSV1 && PFRACTIONALMGR && PVIEWPORTER) {
fractional = makeShared<CCWpFractionalScaleV1>(PFRACTIONALMGR->sendGetFractionalScale(surface->resource()));
fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) {
const bool SAMESCALE = fractionalScale == scale / 120.0;
fractionalScale = scale / 120.0;
Debug::log(LOG, "Got fractional scale: {:.1f}%", fractionalScale * 100.F);
if (!SAMESCALE && readyForFrame)
onScaleUpdate();
});
viewport = makeShared<CCWpViewport>(PVIEWPORTER->sendGetViewport(surface->resource()));
}
fractional = wp_fractional_scale_manager_v1_get_fractional_scale(g_pHyprlock->getFractionalMgr(), surface);
if (fractional) {
wp_fractional_scale_v1_add_listener(fractional, &fsListener, this);
viewport = wp_viewporter_get_viewport(g_pHyprlock->getViewporter(), surface);
} else
if (!PFRACTIONALMGR)
Debug::log(LOG, "No fractional-scale support! Oops, won't be able to scale!");
if (!PVIEWPORTER)
Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!");
lockSurface = ext_session_lock_v1_get_lock_surface(g_pHyprlock->getSessionLock(), surface, output->output);
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource()));
RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1");
if (!surface) {
Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1");
exit(1);
}
ext_session_lock_surface_v1_add_listener(lockSurface, &lockListener, this);
lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) { configure({(double)width, (double)height}, serial); });
}
void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
Debug::log(LOG, "configure with serial {}", serial_);
const bool sameSerial = serial == serial_;
const bool SAMESERIAL = serial == serial_;
const bool SAMESIZE = logicalSize == size_;
const bool SAMESCALE = appliedScale == fractionalScale;
serial = serial_;
size = (size_ * fractionalScale).floor();
logicalSize = size_;
const auto POUTPUT = m_outputRef.lock();
serial = serial_;
logicalSize = size_;
appliedScale = fractionalScale;
if (fractional) {
size = (size_ * fractionalScale).floor();
viewport->sendSetDestination(logicalSize.x, logicalSize.y);
surface->sendSetBufferScale(1);
} else {
size = size_ * POUTPUT->scale;
surface->sendSetBufferScale(POUTPUT->scale);
}
if (!SAMESERIAL)
lockSurface->sendAckConfigure(serial);
Debug::log(LOG, "Configuring surface for logical {} and pixel {}", logicalSize, size);
if (!sameSerial)
ext_session_lock_surface_v1_ack_configure(lockSurface, serial);
if (fractional)
wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y);
wl_surface_set_buffer_scale(surface, 1);
wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF);
if (!eglWindow)
eglWindow = wl_egl_window_create(surface, size.x, size.y);
else
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
surface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
if (!eglWindow) {
Debug::log(CRIT, "Couldn't create eglWindow");
exit(1);
}
if (!eglSurface)
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y);
RASSERT(eglWindow, "Couldn't create eglWindow");
} else
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
if (!eglSurface) {
Debug::log(CRIT, "Couldn't create eglSurface: {}", (int)glGetError());
exit(1);
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
RASSERT(eglSurface, "Couldn't create eglSurface");
}
if (readyForFrame && !(SAMESIZE && SAMESCALE)) {
Debug::log(LOG, "output {} changed, reloading widgets!", POUTPUT->stringPort);
g_pRenderer->reconfigureWidgetsFor(POUTPUT->m_ID);
}
readyForFrame = true;
@ -113,40 +101,45 @@ void CSessionLockSurface::onScaleUpdate() {
configure(logicalSize, serial);
}
static void handleDone(void* data, wl_callback* wl_callback, uint32_t callback_data) {
const auto PSURF = (CSessionLockSurface*)data;
if (g_pHyprlock->m_bTerminate)
return;
PSURF->onCallback();
}
static const wl_callback_listener callbackListener = {
.done = handleDone,
};
void CSessionLockSurface::render() {
Debug::log(TRACE, "render lock");
if (frameCallback || !readyForFrame) {
needsFrame = true;
return;
}
g_pAnimationManager->tick();
const auto FEEDBACK = g_pRenderer->renderLock(*this);
frameCallback = wl_surface_frame(surface);
wl_callback_add_listener(frameCallback, &callbackListener, this);
frameCallback = makeShared<CCWlCallback>(surface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t frameTime) {
if (g_pHyprlock->m_bTerminate)
return;
if (Debug::verbose) {
const auto POUTPUT = m_outputRef.lock();
Debug::log(TRACE, "[{}] frame {}, Current fps: {:.2f}", POUTPUT->stringPort, m_frames, 1000.f / (frameTime - m_lastFrameTime));
}
m_lastFrameTime = frameTime;
m_frames++;
onCallback();
});
eglSwapBuffers(g_pEGL->eglDisplay, eglSurface);
needsFrame = FEEDBACK.needsFrame;
needsFrame = FEEDBACK.needsFrame || g_pAnimationManager->shouldTickForNext();
}
void CSessionLockSurface::onCallback() {
wl_callback_destroy(frameCallback);
frameCallback = nullptr;
frameCallback.reset();
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL)
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL) {
needsFrame = false;
render();
}
}
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
return surface;
}

View file

@ -1,11 +1,12 @@
#pragma once
#include <wayland-client.h>
#include "ext-session-lock-v1-protocol.h"
#include "viewporter-protocol.h"
#include "fractional-scale-v1-protocol.h"
#include "../defines.hpp"
#include "wayland.hpp"
#include "ext-session-lock-v1.hpp"
#include "viewporter.hpp"
#include "fractional-scale-v1.hpp"
#include "../helpers/Math.hpp"
#include <wayland-egl.h>
#include "../helpers/Vector2D.hpp"
#include <EGL/egl.h>
class COutput;
@ -13,35 +14,43 @@ 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;
wl_surface* surface = nullptr;
ext_session_lock_surface_v1* lockSurface = nullptr;
uint32_t serial = 0;
wl_egl_window* eglWindow = nullptr;
Vector2D size;
Vector2D logicalSize;
EGLSurface eglSurface = nullptr;
wp_fractional_scale_v1* fractional = nullptr;
wp_viewport* viewport = nullptr;
WP<COutput> m_outputRef;
OUTPUTID m_outputID = OUTPUT_INVALID;
bool needsFrame = false;
SP<CCWlSurface> surface = nullptr;
SP<CCExtSessionLockSurfaceV1> lockSurface = nullptr;
uint32_t serial = 0;
wl_egl_window* eglWindow = nullptr;
Vector2D size;
Vector2D logicalSize;
float appliedScale;
EGLSurface eglSurface = nullptr;
SP<CCWpFractionalScaleV1> fractional = nullptr;
SP<CCWpViewport> viewport = nullptr;
bool needsFrame = false;
uint32_t m_lastFrameTime = 0;
uint32_t m_frames = 0;
// wayland callbacks
wl_callback* frameCallback = nullptr;
SP<CCWlCallback> frameCallback = nullptr;
friend class CRenderer;
friend class COutput;
};

View file

@ -1,63 +1,69 @@
#include "Output.hpp"
#include "../helpers/Log.hpp"
#include "hyprlock.hpp"
#include "../renderer/Renderer.hpp"
static void handleGeometry(void* data, wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make,
const char* model, int32_t transform) {
const auto POUTPUT = (COutput*)data;
POUTPUT->transform = (wl_output_transform)transform;
void COutput::create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t _name) {
m_ID = _name;
m_wlOutput = pWlOutput;
m_self = pSelf;
Debug::log(LOG, "output {} make {} model {}", POUTPUT->name, make ? make : "", model ? model : "");
m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) {
stringDesc = description ? std::string{description} : "";
Debug::log(LOG, "output {} description {}", m_ID, stringDesc);
});
m_wlOutput->setName([this](CCWlOutput* r, const char* name) {
stringName = std::string{name} + stringName;
stringPort = std::string{name};
Debug::log(LOG, "output {} name {}", name, name);
});
m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
m_wlOutput->setDone([this](CCWlOutput* r) {
done = true;
Debug::log(LOG, "output {} done", m_ID);
if (g_pHyprlock->m_lockAquired && !m_sessionLockSurface) {
Debug::log(LOG, "output {} creating a new lock surface", m_ID);
createSessionLockSurface();
}
});
m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
// handle portrait mode and flipped cases
if (transform % 2 == 1)
size = {height, width};
else
size = {width, height};
});
m_wlOutput->setGeometry(
[this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) {
transform = (wl_output_transform)transform_;
Debug::log(LOG, "output {} make {} model {}", m_ID, make ? make : "", model ? model : "");
});
}
static void handleMode(void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
const auto POUTPUT = (COutput*)data;
// handle portrait mode and flipped cases
if (POUTPUT->transform % 2 == 1)
POUTPUT->size = {height, width};
else
POUTPUT->size = {width, height};
}
static void handleDone(void* data, wl_output* output) {
const auto POUTPUT = (COutput*)data;
Debug::log(LOG, "output {} done", POUTPUT->name);
if (g_pHyprlock->m_bLocked && !POUTPUT->sessionLockSurface) {
// if we are already locked, create a surface dynamically
Debug::log(LOG, "Creating a surface dynamically for output as we are already locked");
POUTPUT->sessionLockSurface = std::make_unique<CSessionLockSurface>(POUTPUT);
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());
}
static void handleScale(void* data, wl_output* output, int32_t factor) {
const auto POUTPUT = (COutput*)data;
POUTPUT->scale = factor;
}
static void handleName(void* data, wl_output* output, const char* name) {
const auto POUTPUT = (COutput*)data;
POUTPUT->stringName = std::string{name} + POUTPUT->stringName;
POUTPUT->stringPort = std::string{name};
Debug::log(LOG, "output {} name {}", POUTPUT->name, name);
}
static void handleDescription(void* data, wl_output* output, const char* description) {
const auto POUTPUT = (COutput*)data;
POUTPUT->stringDesc = description ? std::string{description} : "";
Debug::log(LOG, "output {} description {}", POUTPUT->name, POUTPUT->stringDesc);
}
static const wl_output_listener outputListener = {
.geometry = handleGeometry,
.mode = handleMode,
.done = handleDone,
.scale = handleScale,
.name = handleName,
.description = handleDescription,
};
COutput::COutput(wl_output* output, uint32_t name) : name(name), output(output) {
wl_output_add_listener(output, &outputListener, this);
Vector2D COutput::getViewport() const {
return (m_sessionLockSurface) ? m_sessionLockSurface->size : size;
}

View file

@ -1,26 +1,33 @@
#pragma once
#include <wayland-client.h>
#include "../helpers/Vector2D.hpp"
#include "../defines.hpp"
#include "wayland.hpp"
#include "LockSurface.hpp"
#include <memory>
class COutput {
public:
COutput(wl_output* output, uint32_t name);
COutput() = default;
~COutput() = default;
uint32_t name = 0;
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 = "";
wl_output* output = nullptr;
UP<CSessionLockSurface> m_sessionLockSurface;
private:
SP<CCWlOutput> m_wlOutput = nullptr;
WP<COutput> m_self;
void createSessionLockSurface();
Vector2D getViewport() const;
};

163
src/core/Seat.cpp Normal file
View file

@ -0,0 +1,163 @@
#include "Seat.hpp"
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include <chrono>
#include <sys/mman.h>
#include <unistd.h>
CSeatManager::~CSeatManager() {
if (m_pXKBState)
xkb_state_unref(m_pXKBState);
if (m_pXKBKeymap)
xkb_keymap_unref(m_pXKBKeymap);
if (m_pXKBContext)
xkb_context_unref(m_pXKBContext);
}
void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
m_pSeat = seat;
m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!m_pXKBContext)
Debug::log(ERR, "Failed to create xkb context");
m_pSeat->setCapabilities([this](CCWlSeat* r, wl_seat_capability caps) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());
static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (!*HIDECURSOR)
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);
if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
return;
if (!g_pHyprlock->isUnlocked() && g_pHyprlock->m_vLastEnterCoords.distance({wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)}) > 5) {
Debug::log(LOG, "In grace and cursor moved more than 5px, unlocking!");
g_pHyprlock->unlock();
}
});
m_pPointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surf, wl_fixed_t surface_x, wl_fixed_t surface_y) {
if (!m_pCursorShape)
return;
m_pCursorShape->lastCursorSerial = serial;
if (*HIDECURSOR)
m_pCursorShape->hideCursor();
else
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (*HIDECURSOR)
return;
for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
if (!POUTPUT->m_sessionLockSurface)
continue;
const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
if (PWLSURFACE->resource() == surf)
g_pHyprlock->m_focusedOutput = POUTPUT;
}
});
m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });
m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
if (*HIDECURSOR)
return;
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
});
}
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
m_pKeeb = makeShared<CCWlKeyboard>(r->sendGetKeyboard());
m_pKeeb->setKeymap([this](CCWlKeyboard*, wl_keyboard_keymap_format format, int32_t fd, uint32_t size) {
if (!m_pXKBContext)
return;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
Debug::log(ERR, "Could not recognise keymap format");
return;
}
const char* buf = (const char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
Debug::log(ERR, "Failed to mmap xkb keymap: {}", errno);
return;
}
m_pXKBKeymap = xkb_keymap_new_from_buffer(m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap((void*)buf, size);
close(fd);
if (!m_pXKBKeymap) {
Debug::log(ERR, "Failed to compile xkb keymap");
return;
}
m_pXKBState = xkb_state_new(m_pXKBKeymap);
if (!m_pXKBState) {
Debug::log(ERR, "Failed to create xkb state");
return;
}
const auto PCOMOPOSETABLE = xkb_compose_table_new_from_locale(m_pXKBContext, setlocale(LC_CTYPE, nullptr), XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!PCOMOPOSETABLE) {
Debug::log(ERR, "Failed to create xkb compose table");
return;
}
m_pXKBComposeState = xkb_compose_state_new(PCOMOPOSETABLE, XKB_COMPOSE_STATE_NO_FLAGS);
});
m_pKeeb->setKey([](CCWlKeyboard* r, uint32_t serial, uint32_t time, uint32_t key, wl_keyboard_key_state state) {
g_pHyprlock->onKey(key, state == WL_KEYBOARD_KEY_STATE_PRESSED);
});
m_pKeeb->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
if (!m_pXKBState)
return;
if (group != g_pHyprlock->m_uiActiveLayout) {
g_pHyprlock->m_uiActiveLayout = group;
for (auto& t : g_pHyprlock->getTimers()) {
if (t->canForceUpdate()) {
t->call(t);
t->cancel();
}
}
}
xkb_state_update_mask(m_pXKBState, mods_depressed, mods_latched, mods_locked, 0, 0, group);
g_pHyprlock->m_bCapsLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
g_pHyprlock->m_bNumLock = xkb_state_mod_name_is_active(m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED);
});
m_pKeeb->setRepeatInfo([](CCWlKeyboard* r, int32_t rate, int32_t delay) {
g_pHyprlock->m_iKeebRepeatRate = rate;
g_pHyprlock->m_iKeebRepeatDelay = delay;
});
}
});
m_pSeat->setName([](CCWlSeat* r, const char* name) { Debug::log(LOG, "Exposed seat name: {}", name ? name : "nullptr"); });
}
void CSeatManager::registerCursorShape(SP<CCWpCursorShapeManagerV1> shape) {
m_pCursorShape = makeUnique<CCursorShape>(shape);
}
bool CSeatManager::registered() {
return m_pSeat;
}

32
src/core/Seat.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include "../defines.hpp"
#include "CursorShape.hpp"
#include "wayland.hpp"
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
class CSeatManager {
public:
CSeatManager() = default;
~CSeatManager();
void registerSeat(SP<CCWlSeat> seat);
void registerCursorShape(SP<CCWpCursorShapeManagerV1> shape);
bool registered();
SP<CCWlKeyboard> m_pKeeb;
SP<CCWlPointer> m_pPointer;
UP<CCursorShape> m_pCursorShape;
xkb_context* m_pXKBContext = nullptr;
xkb_keymap* m_pXKBKeymap = nullptr;
xkb_state* m_pXKBState = nullptr;
xkb_compose_state* m_pXKBComposeState = nullptr;
private:
SP<CCWlSeat> m_pSeat;
};
inline UP<CSeatManager> g_pSeatManager = makeUnique<CSeatManager>();

View file

@ -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(std::shared_ptr<CTimer> self, void* data)> cb_, void* data_, bool force) :
cb(cb_), data(data_), allowForceUpdate(force) {
expires = std::chrono::system_clock::now() + timeout;
}
bool CTimer::passed() {

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,23 @@
#pragma once
#include <wayland-client.h>
#include "ext-session-lock-v1-protocol.h"
#include "fractional-scale-v1-protocol.h"
#include "linux-dmabuf-unstable-v1-protocol.h"
#include "wlr-screencopy-unstable-v1-protocol.h"
#include "viewporter-protocol.h"
#include "../defines.hpp"
#include "wayland.hpp"
#include "ext-session-lock-v1.hpp"
#include "fractional-scale-v1.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
#include "linux-dmabuf-v1.hpp"
#include "viewporter.hpp"
#include "Output.hpp"
#include "Seat.hpp"
#include "CursorShape.hpp"
#include "Timer.hpp"
#include "Auth.hpp"
#include <memory>
#include <vector>
#include <condition_variable>
#include <optional>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#include <gbm.h>
#include <xf86drm.h>
@ -28,117 +29,112 @@ struct SDMABUFModifier {
class CHyprlock {
public:
CHyprlock(const std::string& wlDisplay, const bool immediate);
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender);
~CHyprlock();
void run();
void run();
void unlock();
void unlock();
bool isUnlocked();
void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name);
std::shared_ptr<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data,
bool force = false);
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 attemptRestoreOnDeath();
void renderOutput(const std::string& stringPort);
void renderAllOutputs();
void spawnAsync(const std::string& cmd);
std::string spawnSync(const std::string& cmd);
size_t getPasswordBufferLen();
size_t getPasswordBufferDisplayLen();
void onKey(uint32_t key, bool down);
void handleKeySym(xkb_keysym_t sym);
void startKeyRepeat(xkb_keysym_t sym);
void repeatKey(xkb_keysym_t sym);
void onPasswordCheckTimer();
void clearPasswordBuffer();
bool passwordCheckWaiting();
std::optional<std::string> passwordLastFailReason();
SP<CCExtSessionLockManagerV1> getSessionLockMgr();
SP<CCExtSessionLockV1> getSessionLock();
SP<CCWlCompositor> getCompositor();
wl_display* getDisplay();
SP<CCWpFractionalScaleManagerV1> getFractionalMgr();
SP<CCWpViewporter> getViewporter();
SP<CCZwlrScreencopyManagerV1> getScreencopy();
SP<CCWlShm> getShm();
void renderOutput(const std::string& stringPort);
void renderAllOutputs();
int32_t m_iKeebRepeatRate = 25;
int32_t m_iKeebRepeatDelay = 600;
size_t getPasswordBufferLen();
size_t getPasswordBufferDisplayLen();
size_t getPasswordFailedAttempts();
xkb_layout_index_t m_uiActiveLayout = 0;
ext_session_lock_manager_v1* getSessionLockMgr();
ext_session_lock_v1* getSessionLock();
wl_compositor* getCompositor();
wl_display* getDisplay();
wp_fractional_scale_manager_v1* getFractionalMgr();
wp_viewporter* getViewporter();
zwlr_screencopy_manager_v1* getScreencopy();
bool m_bTerminate = false;
wl_pointer* m_pPointer = nullptr;
wl_keyboard* m_pKeeb = nullptr;
bool m_lockAquired = false;
bool m_bLocked = false;
std::unique_ptr<CCursorShape> m_pCursorShape;
bool m_bCapsLock = false;
bool m_bNumLock = false;
bool m_bCtrl = false;
xkb_context* m_pXKBContext = nullptr;
xkb_keymap* m_pXKBKeymap = nullptr;
xkb_state* m_pXKBState = nullptr;
bool m_bImmediateRender = false;
int32_t m_iKeebRepeatRate = 25;
int32_t m_iKeebRepeatDelay = 600;
std::string m_sCurrentDesktop = "";
xkb_layout_index_t m_uiActiveLayout = 0;
bool m_bTerminate = false;
bool m_bLocked = false;
bool m_bCapsLock = false;
bool m_bNumLock = false;
bool m_bCtrl = false;
bool m_bFadeStarted = false;
//
std::chrono::system_clock::time_point m_tGraceEnds;
std::chrono::system_clock::time_point m_tFadeEnds;
Vector2D m_vLastEnterCoords = {};
WP<COutput> m_focusedOutput;
Vector2D m_vMouseLocation = {};
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;
std::vector<std::unique_ptr<COutput>> m_vOutputs;
std::vector<SP<COutput>> m_vOutputs;
std::vector<std::shared_ptr<CTimer>> getTimers();
struct {
void* linuxDmabuf = nullptr;
void* linuxDmabufFeedback = nullptr;
SP<CCZwpLinuxDmabufV1> linuxDmabuf = nullptr;
SP<CCZwpLinuxDmabufFeedbackV1> linuxDmabufFeedback = nullptr;
gbm_bo* gbm = nullptr;
gbm_device* gbmDevice = nullptr;
gbm_bo* gbm = nullptr;
gbm_device* gbmDevice = nullptr;
void* formatTable = nullptr;
size_t formatTableSize = 0;
bool deviceUsed = false;
void* formatTable = nullptr;
size_t formatTableSize = 0;
bool deviceUsed = false;
std::vector<SDMABUFModifier> dmabufMods;
std::vector<SDMABUFModifier> dmabufMods;
} dma;
gbm_device* createGBMDevice(drmDevice* dev);
private:
struct {
wl_display* display = nullptr;
wl_registry* registry = nullptr;
wl_seat* seat = nullptr;
ext_session_lock_manager_v1* sessionLock = nullptr;
wl_compositor* compositor = nullptr;
wp_fractional_scale_manager_v1* fractional = nullptr;
wp_viewporter* viewporter = nullptr;
zwlr_screencopy_manager_v1* screencopy = nullptr;
wl_display* display = nullptr;
SP<CCWlRegistry> registry = nullptr;
SP<CCExtSessionLockManagerV1> sessionLock = nullptr;
SP<CCWlCompositor> compositor = nullptr;
SP<CCWpFractionalScaleManagerV1> fractional = nullptr;
SP<CCWpViewporter> viewporter = nullptr;
SP<CCZwlrScreencopyManagerV1> screencopy = nullptr;
SP<CCWlShm> shm = nullptr;
} m_sWaylandState;
void addDmabufListener();
struct {
ext_session_lock_v1* lock = nullptr;
SP<CCExtSessionLockV1> lock = nullptr;
} m_sLockState;
struct {
@ -154,14 +150,19 @@ class CHyprlock {
std::condition_variable loopCV;
bool event = false;
std::condition_variable wlDispatchCV;
bool wlDispatched = false;
std::condition_variable timerCV;
std::mutex timerRequestMutex;
bool timerEvent = false;
} m_sLoopState;
bool m_bUnlockedCalled = false;
std::vector<std::shared_ptr<CTimer>> m_vTimers;
std::vector<uint32_t> m_vPressedKeys;
};
inline std::unique_ptr<CHyprlock> g_pHyprlock;
inline UP<CHyprlock> g_pHyprlock;

14
src/defines.hpp Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprgraphics/color/Color.hpp>
using namespace Hyprutils::Memory;
using namespace Hyprgraphics;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
typedef int64_t OUTPUTID;
constexpr OUTPUTID OUTPUT_INVALID = -1;

View file

@ -0,0 +1,67 @@
#pragma once
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "Color.hpp"
#include "Math.hpp"
#include "../defines.hpp"
#include "../config/ConfigDataValues.hpp"
enum eAnimatedVarType {
AVARTYPE_INVALID = -1,
AVARTYPE_FLOAT,
AVARTYPE_VECTOR,
AVARTYPE_COLOR,
AVARTYPE_GRADIENT
};
// Utility to bind a type with its corresponding eAnimatedVarType
template <class T>
// NOLINTNEXTLINE(readability-identifier-naming)
struct STypeToAnimatedVarType_t {
static constexpr eAnimatedVarType value = AVARTYPE_INVALID;
};
template <>
struct STypeToAnimatedVarType_t<float> {
static constexpr eAnimatedVarType value = AVARTYPE_FLOAT;
};
template <>
struct STypeToAnimatedVarType_t<Vector2D> {
static constexpr eAnimatedVarType value = AVARTYPE_VECTOR;
};
template <>
struct STypeToAnimatedVarType_t<CHyprColor> {
static constexpr eAnimatedVarType value = AVARTYPE_COLOR;
};
template <>
struct STypeToAnimatedVarType_t<CGradientValueData> {
static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT;
};
template <class T>
inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t<T>::value;
// Utility to define a concept as a list of possible type
template <class T, class... U>
concept OneOf = (... or std::same_as<T, U>);
// Concept to describe which type can be placed into CAnimatedVariable
// This is mainly to get better errors if we put a type that's not supported
// Otherwise template errors are ugly
template <class T>
concept Animable = OneOf<T, Vector2D, float, CHyprColor, CGradientValueData>;
struct SAnimationContext {};
template <Animable VarType>
using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable<VarType, SAnimationContext>;
template <Animable VarType>
using PHLANIMVAR = SP<CAnimatedVariable<VarType>>;
template <Animable VarType>
using PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;

View file

@ -1,101 +0,0 @@
#include "Box.hpp"
#include <limits>
#include <algorithm>
#define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x <= (x2) && (vec).y >= (y1) && (vec).y <= (y2))
CBox& CBox::scale(double scale) {
x *= scale;
y *= scale;
w *= scale;
h *= scale;
return *this;
}
CBox& CBox::scale(const Vector2D& scale) {
x *= scale.x;
y *= scale.y;
w *= scale.x;
h *= scale.y;
return *this;
}
CBox& CBox::translate(const Vector2D& vec) {
x += vec.x;
y += vec.y;
return *this;
}
Vector2D CBox::middle() const {
return Vector2D{x + w / 2.0, y + h / 2.0};
}
bool CBox::containsPoint(const Vector2D& vec) const {
return VECINRECT(vec, x, y, x + w, y + h);
}
bool CBox::empty() const {
return w == 0 || h == 0;
}
CBox& CBox::round() {
float newW = x + w - std::round(x);
float newH = y + h - std::round(y);
x = std::round(x);
y = std::round(y);
w = std::round(newW);
h = std::round(newH);
return *this;
}
CBox& CBox::scaleFromCenter(double scale) {
double oldW = w, oldH = h;
w *= scale;
h *= scale;
x -= (w - oldW) / 2.0;
y -= (h - oldH) / 2.0;
return *this;
}
CBox& CBox::expand(const double& value) {
x -= value;
y -= value;
w += value * 2.0;
h += value * 2.0;
return *this;
}
CBox& CBox::noNegativeSize() {
std::clamp(w, 0.0, std::numeric_limits<double>::infinity());
std::clamp(h, 0.0, std::numeric_limits<double>::infinity());
return *this;
}
CBox CBox::roundInternal() {
float newW = x + w - std::floor(x);
float newH = y + h - std::floor(y);
return CBox{std::floor(x), std::floor(y), std::floor(newW), std::floor(newH)};
}
CBox CBox::copy() const {
return CBox{*this};
}
Vector2D CBox::pos() const {
return {x, y};
}
Vector2D CBox::size() const {
return {w, h};
}

View file

@ -1,69 +0,0 @@
#pragma once
#include "Vector2D.hpp"
class CBox {
public:
CBox(double x_, double y_, double w_, double h_) {
x = x_;
y = y_;
w = w_;
h = h_;
}
CBox() {
w = 0;
h = 0;
}
CBox(const double d) {
x = d;
y = d;
w = d;
h = d;
}
CBox(const Vector2D& pos, const Vector2D& size) {
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
CBox& scale(const Vector2D& scale);
CBox& translate(const Vector2D& vec);
CBox& round();
CBox& expand(const double& value);
CBox& noNegativeSize();
CBox copy() const;
Vector2D middle() const;
Vector2D pos() const;
Vector2D size() const;
bool containsPoint(const Vector2D& vec) const;
bool empty() const;
double x = 0, y = 0;
union {
double w;
double width;
};
union {
double h;
double height;
};
double rot = 0; /* rad, ccw */
//
bool operator==(const CBox& rhs) const {
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
}
private:
CBox roundInternal();
};

View file

@ -5,22 +5,43 @@
#define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0)
#define BLUE(c) ((double)(((c)) & 0xff) / 255.0)
CColor::CColor() {}
CColor::CColor(float r, float g, float b, float a) {
this->r = r;
this->g = g;
this->b = b;
this->a = a;
CHyprColor::CHyprColor() {
;
}
CColor::CColor(uint64_t hex) {
this->r = RED(hex);
this->g = GREEN(hex);
this->b = BLUE(hex);
this->a = ALPHA(hex);
CHyprColor::CHyprColor(float r_, float g_, float b_, float a_) : r(r_), g(g_), b(b_), a(a_) {
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab();
}
uint64_t CColor::getAsHex() {
return ((int)a) * 0x1000000 + ((int)r) * 0x10000 + ((int)g) * 0x100 + ((int)b) * 0x1;
CHyprColor::CHyprColor(uint64_t hex) : r(RED(hex)), g(GREEN(hex)), b(BLUE(hex)), a(ALPHA(hex)) {
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{.r = r, .g = g, .b = b}).asOkLab();
}
CHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) : a(a_) {
const auto SRGB = color.asRgb();
r = SRGB.r;
g = SRGB.g;
b = SRGB.b;
okLab = color.asOkLab();
}
uint32_t CHyprColor::getAsHex() const {
return ((uint32_t)(a * 255.f) * 0x1000000) + ((uint32_t)(r * 255.f) * 0x10000) + ((uint32_t)(g * 255.f) * 0x100) + ((uint32_t)(b * 255.f) * 0x1);
}
Hyprgraphics::CColor::SSRGB CHyprColor::asRGB() const {
return {.r = r, .g = g, .b = b};
}
Hyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const {
return okLab;
}
Hyprgraphics::CColor::SHSL CHyprColor::asHSL() const {
return Hyprgraphics::CColor(okLab).asHSL();
}
CHyprColor CHyprColor::stripA() const {
return {r, g, b, 1.F};
}

View file

@ -1,34 +1,46 @@
#pragma once
#include <cstdint>
#include "../helpers/Log.hpp"
#include <hyprgraphics/color/Color.hpp>
class CColor {
class CHyprColor {
public:
CColor();
CColor(float r, float g, float b, float a);
CColor(uint64_t);
CHyprColor();
CHyprColor(float r, float g, float b, float a);
CHyprColor(const Hyprgraphics::CColor& col, float a);
CHyprColor(uint64_t);
float r = 0, g = 0, b = 0, a = 1.f;
// AR32
uint32_t getAsHex() const;
Hyprgraphics::CColor::SSRGB asRGB() const;
Hyprgraphics::CColor::SOkLab asOkLab() const;
Hyprgraphics::CColor::SHSL asHSL() const;
CHyprColor stripA() const;
uint64_t getAsHex();
CColor operator-(const CColor& c2) const {
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
//
bool operator==(const CHyprColor& c2) const {
return c2.r == r && c2.g == g && c2.b == b && c2.a == a;
}
CColor operator+(const CColor& c2) const {
return CColor(r + c2.r, g + c2.g, b + c2.b, a + c2.a);
// stubs for the AnimationMgr
CHyprColor operator-(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: - is a STUB");
return {};
}
CColor operator*(const float& v) const {
return CColor(r * v, g * v, b * v, a * v);
CHyprColor operator+(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: + is a STUB");
return {};
}
bool operator==(const CColor& c2) const {
return r == c2.r && g == c2.g && b == c2.b && a == c2.a;
CHyprColor operator*(const float& c2) const {
RASSERT(false, "CHyprColor: * is a STUB");
return {};
}
CColor stripA() const {
return {r, g, b, 1};
}
double r = 0, g = 0, b = 0, a = 0;
private:
Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation
};

View file

@ -1,76 +0,0 @@
#include "Jpeg.hpp"
#include "Log.hpp"
#include <jpeglib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??");
return nullptr;
}
if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) {
Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy.");
return nullptr;
}
void* imageRawData;
struct stat fileInfo = {};
const auto FD = open(path.c_str(), O_RDONLY);
fstat(FD, &fileInfo);
imageRawData = malloc(fileInfo.st_size);
read(FD, imageRawData, fileInfo.st_size);
close(FD);
// now the JPEG is in the memory
jpeg_decompress_struct decompressStruct = {};
jpeg_error_mgr errorManager = {};
decompressStruct.err = jpeg_std_error(&errorManager);
jpeg_create_decompress(&decompressStruct);
jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size);
jpeg_read_header(&decompressStruct, true);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
decompressStruct.out_color_space = JCS_EXT_BGRA;
#else
decompressStruct.out_color_space = JCS_EXT_ARGB;
#endif
// decompress
jpeg_start_decompress(&decompressStruct);
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)");
return nullptr;
}
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
JSAMPROW rowRead;
while (decompressStruct.output_scanline < decompressStruct.output_height) {
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
rowRead = PROW;
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
}
cairo_surface_flush(cairoSurface);
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
jpeg_finish_decompress(&decompressStruct);
jpeg_destroy_decompress(&decompressStruct);
return cairoSurface;
}

View file

@ -1,8 +0,0 @@
#pragma once
#include <filesystem>
#include <cairo/cairo.h>
namespace JPEG {
cairo_surface_t* createSurfaceFromJPEG(const std::filesystem::path&);
};

View file

@ -1,7 +1,7 @@
#pragma once
#include <format>
#include <iostream>
#include <string>
#include <print>
enum eLogLevel {
TRACE = 0,
@ -18,13 +18,23 @@ enum eLogLevel {
Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \
std::format(reason, ##__VA_ARGS__), __LINE__, \
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \
*((int*)nullptr) = 1; /* so that we crash and get a coredump */ \
std::abort(); \
}
#define ASSERT(expr) RASSERT(expr, "?")
namespace Debug {
constexpr const char* logLevelString(eLogLevel level) {
switch (level) {
case TRACE: return "TRACE"; break;
case INFO: return "INFO"; break;
case LOG: return "LOG"; break;
case WARN: return "WARN"; break;
case ERR: return "ERR"; break;
case CRIT: return "CRITICAL"; break;
default: return "??";
}
}
inline bool quiet = false;
inline bool verbose = false;
@ -38,21 +48,7 @@ namespace Debug {
return;
if (level != NONE) {
std::cout << '[';
switch (level) {
case TRACE: std::cout << "TRACE"; break;
case INFO: std::cout << "INFO"; break;
case LOG: std::cout << "LOG"; break;
case WARN: std::cout << "WARN"; break;
case ERR: std::cout << "ERR"; break;
case CRIT: std::cout << "CRITICAL"; break;
default: break;
}
std::cout << "] ";
std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...)));
}
std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n";
}
};

23
src/helpers/Math.cpp Normal file
View file

@ -0,0 +1,23 @@
#include "Math.hpp"
Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) {
switch (t) {
case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL;
case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180;
case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90;
case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270;
case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED;
case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180;
case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270;
case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90;
default: break;
}
return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL;
}
wl_output_transform invertTransform(wl_output_transform tr) {
if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED))
tr = (wl_output_transform)(tr ^ (int)WL_OUTPUT_TRANSFORM_180);
return tr;
}

12
src/helpers/Math.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <wayland-client.h>
#include <hyprutils/math/Box.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/math/Mat3x3.hpp>
using namespace Hyprutils::Math;
eTransform wlTransformToHyprutils(wl_output_transform t);
wl_output_transform invertTransform(wl_output_transform tr);

View file

@ -1,5 +1,15 @@
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <fcntl.h>
#include "MiscFunctions.hpp"
#include "Log.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
#include <unistd.h>
using namespace Hyprutils::String;
using namespace Hyprutils::OS;
std::string absolutePath(const std::string& rawpath, const std::string& currentDir) {
std::filesystem::path path(rawpath);
@ -7,12 +17,144 @@ std::string absolutePath(const std::string& rawpath, const std::string& currentD
// Handling where rawpath starts with '~'
if (!rawpath.empty() && rawpath[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
return std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2);
path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2);
}
// Handling e.g. ./, ../
else if (path.is_relative()) {
if (path.is_relative()) {
return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path);
} else {
return std::filesystem::weakly_canonical(path);
}
}
int64_t configStringToInt(const std::string& VALUE) {
auto parseHex = [](const std::string& value) -> int64_t {
try {
size_t position;
auto result = stoll(value, &position, 16);
if (position == value.size())
return result;
} catch (const std::exception&) {}
throw std::invalid_argument("invalid hex " + value);
};
if (VALUE.starts_with("0x")) {
// Values with 0x are hex
return parseHex(VALUE);
} else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6));
// try doing it the comma way first
if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 3) {
// cool
std::string rolling = VALUEWITHOUTFUNC;
auto r = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto g = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto b = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
uint8_t a = 0;
try {
a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f);
} catch (std::exception& e) { throw std::invalid_argument("failed parsing " + VALUEWITHOUTFUNC); }
return (a * (Hyprlang::INT)0x1000000) + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b;
} else if (VALUEWITHOUTFUNC.length() == 8) {
const auto RGBA = parseHex(VALUEWITHOUTFUNC);
// now we need to RGBA -> ARGB. The config holds ARGB only.
return (RGBA >> 8) + (0x1000000 * (RGBA & 0xFF));
}
throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values");
} else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5));
// try doing it the comma way first
if (std::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 2) {
// cool
std::string rolling = VALUEWITHOUTFUNC;
auto r = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto g = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
rolling = rolling.substr(rolling.find(',') + 1);
auto b = configStringToInt(trim(rolling.substr(0, rolling.find(','))));
return (Hyprlang::INT)0xFF000000 + (r * (Hyprlang::INT)0x10000) + (g * (Hyprlang::INT)0x100) + b;
} else if (VALUEWITHOUTFUNC.length() == 6) {
return parseHex(VALUEWITHOUTFUNC) + 0xFF000000;
}
throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes) or 3 comma separated values");
} else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) {
return 1;
} else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) {
return 0;
}
if (VALUE.empty() || !isNumber(VALUE, false))
throw std::invalid_argument("cannot parse \"" + VALUE + "\" as an int.");
try {
const auto RES = std::stoll(VALUE);
return RES;
} catch (std::exception& e) { throw std::invalid_argument(std::string{"stoll threw: "} + e.what()); }
return 0;
}
int createPoolFile(size_t size, std::string& name) {
const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
if (!XDGRUNTIMEDIR) {
Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
return -1;
}
name = std::string(XDGRUNTIMEDIR) + "/.hyprlock_sc_XXXXXX";
const auto FD = mkstemp((char*)name.c_str());
if (FD < 0) {
Debug::log(CRIT, "createPoolFile: fd < 0");
return -1;
}
// set cloexec
long flags = fcntl(FD, F_GETFD);
if (flags == -1) {
close(FD);
return -1;
}
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
close(FD);
Debug::log(CRIT, "createPoolFile: fcntl < 0");
return -1;
}
if (ftruncate(FD, size) < 0) {
close(FD);
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
return -1;
}
return FD;
}
std::string spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}
if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
return proc.stdOut();
}
void spawnAsync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runAsync())
Debug::log(ERR, "Failed to start \"{}\"", cmd);
}

View file

@ -1,6 +1,11 @@
#pragma once
#include <string>
#include <optional>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
std::string absolutePath(const std::string&, const std::string&);
int64_t configStringToInt(const std::string& VALUE);
int createPoolFile(size_t size, std::string& name);
std::string spawnSync(const std::string& cmd);
void spawnAsync(const std::string& cmd);

View file

@ -1,57 +0,0 @@
#include "Vector2D.hpp"
#include <algorithm>
#include <cmath>
Vector2D::Vector2D(double xx, double yy) {
x = xx;
y = yy;
}
Vector2D::Vector2D() {
x = 0;
y = 0;
}
Vector2D::~Vector2D() {}
double Vector2D::normalize() {
// get max abs
const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y);
x /= max;
y /= max;
return max;
}
Vector2D Vector2D::floor() const {
return Vector2D(std::floor(x), std::floor(y));
}
Vector2D Vector2D::round() const {
return Vector2D(std::round(x), std::round(y));
}
Vector2D Vector2D::clamp(const Vector2D& min, const Vector2D& max) const {
return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y));
}
double Vector2D::distance(const Vector2D& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
double Vector2D::size() const {
return std::sqrt(x * x + y * y);
}
Vector2D Vector2D::getComponentMax(const Vector2D& other) const {
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
}
Vector2D Vector2D::rotated(const double& ang) const {
const double COS = std::abs(std::cos(ang));
const double SIN = std::abs(std::sin(ang));
return Vector2D(x * COS + y * SIN, x * SIN + y * COS);
}

View file

@ -1,160 +0,0 @@
#pragma once
#include <cmath>
#include <format>
#include <hyprlang.hpp>
class Vector2D {
public:
Vector2D(double, double);
Vector2D();
Vector2D(const Hyprlang::VEC2& v) {
x = v.x;
y = v.y;
}
~Vector2D();
double x = 0;
double y = 0;
// returns the scale
double normalize();
Vector2D operator+(const Vector2D& a) const {
return Vector2D(this->x + a.x, this->y + a.y);
}
Vector2D operator-(const Vector2D& a) const {
return Vector2D(this->x - a.x, this->y - a.y);
}
Vector2D operator-() const {
return Vector2D(-this->x, -this->y);
}
Vector2D operator*(const double& a) const {
return Vector2D(this->x * a, this->y * a);
}
Vector2D operator/(const double& a) const {
return Vector2D(this->x / a, this->y / a);
}
bool operator==(const Vector2D& a) const {
return a.x == x && a.y == y;
}
bool operator!=(const Vector2D& a) const {
return a.x != x || a.y != y;
}
Vector2D operator*(const Vector2D& a) const {
return Vector2D(this->x * a.x, this->y * a.y);
}
Vector2D operator/(const Vector2D& a) const {
return Vector2D(this->x / a.x, this->y / a.y);
}
bool operator>(const Vector2D& a) const {
return this->x > a.x && this->y > a.y;
}
bool operator<(const Vector2D& a) const {
return this->x < a.x && this->y < a.y;
}
Vector2D& operator+=(const Vector2D& a) {
this->x += a.x;
this->y += a.y;
return *this;
}
Vector2D& operator-=(const Vector2D& a) {
this->x -= a.x;
this->y -= a.y;
return *this;
}
Vector2D& operator*=(const Vector2D& a) {
this->x *= a.x;
this->y *= a.y;
return *this;
}
Vector2D& operator/=(const Vector2D& a) {
this->x /= a.x;
this->y /= a.y;
return *this;
}
Vector2D& operator*=(const double& a) {
this->x *= a;
this->y *= a;
return *this;
}
Vector2D& operator/=(const double& a) {
this->x /= a;
this->y /= a;
return *this;
}
double distance(const Vector2D& other) const;
double size() const;
Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const;
Vector2D floor() const;
Vector2D round() const;
Vector2D getComponentMax(const Vector2D& other) const;
Vector2D rotated(const double& ang) const;
};
/**
format specification
- 'j', as json array
- 'X', same as std::format("{}x{}", vec.x, vec.y)
- number, floating point precision, use `0` to format as integer
*/
// absolutely ridiculous formatter spec parsing
#define FORMAT_PARSE(specs__, type__) \
template <typename FormatContext> \
constexpr auto parse(FormatContext& ctx) { \
auto it = ctx.begin(); \
for (; it != ctx.end() && *it != '}'; it++) { \
switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \
} \
return it; \
}
#define FORMAT_FLAG(spec__, flag__) \
case spec__: (flag__) = true; break;
#define FORMAT_NUMBER(buf__) \
case '0': \
case '1': \
case '2': \
case '3': \
case '4': \
case '5': \
case '6': \
case '7': \
case '8': \
case '9': (buf__).push_back(*it); break;
template <typename CharT>
struct std::formatter<Vector2D, CharT> : std::formatter<CharT> {
bool formatJson = false;
bool formatX = false;
std::string precision = "";
FORMAT_PARSE(FORMAT_FLAG('j', formatJson) //
FORMAT_FLAG('X', formatX) //
FORMAT_NUMBER(precision),
Vector2D)
template <typename FormatContext>
auto format(const Vector2D& vec, FormatContext& ctx) const {
std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision);
if (formatJson)
formatString = std::format("[{0}, {0}]", formatString);
else if (formatX)
formatString = std::format("{0}x{0}", formatString);
else
formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString);
try {
string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y));
return std::format_to(ctx.out(), "{}", buf);
} catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); }
}
};

View file

@ -1,84 +0,0 @@
#include "Webp.hpp"
#include "Log.hpp"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <webp/decode.h>
cairo_surface_t* WEBP::createSurfaceFromWEBP(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
Debug::log(ERR, "createSurfaceFromWEBP: file doesn't exist??");
return nullptr;
}
void* imageRawData;
struct stat fileInfo = {};
const auto FD = open(path.c_str(), O_RDONLY);
fstat(FD, &fileInfo);
imageRawData = malloc(fileInfo.st_size);
read(FD, imageRawData, fileInfo.st_size);
close(FD);
// now the WebP is in the memory
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) {
Debug::log(CRIT, "WebPInitDecoderConfig Failed");
return nullptr;
}
if (WebPGetFeatures((const unsigned char*)imageRawData, fileInfo.st_size, &config.input) != VP8_STATUS_OK) {
Debug::log(ERR, "createSurfaceFromWEBP: file is not webp format");
free(imageRawData);
return nullptr;
}
const auto HEIGHT = config.input.height;
const auto WIDTH = config.input.width;
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
Debug::log(CRIT, "createSurfaceFromWEBP: Cairo Failed (?)");
cairo_surface_destroy(cairoSurface);
return nullptr;
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
config.output.colorspace = MODE_bgrA;
#else
config.output.colorspace = MODE_Argb;
#endif
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
config.options.no_fancy_upsampling = 1;
config.output.u.RGBA.rgba = CAIRODATA;
config.output.u.RGBA.stride = CAIROSTRIDE;
config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT;
config.output.is_external_memory = 1;
config.output.width = WIDTH;
config.output.height = HEIGHT;
if (WebPDecode((const unsigned char*)imageRawData, fileInfo.st_size, &config) != VP8_STATUS_OK) {
Debug::log(CRIT, "createSurfaceFromWEBP: WebP Decode Failed (?)");
return nullptr;
}
cairo_surface_flush(cairoSurface);
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_PNG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
WebPFreeDecBuffer(&config.output);
return cairoSurface;
}

View file

@ -1,8 +0,0 @@
#pragma once
#include <filesystem>
#include <cairo/cairo.h>
namespace WEBP {
cairo_surface_t* createSurfaceFromWEBP(const std::filesystem::path&);
};

View file

@ -1,39 +1,64 @@
#include "config/ConfigManager.hpp"
#include "core/hyprlock.hpp"
#include "helpers/Log.hpp"
#include "core/AnimationManager.hpp"
#include <cstddef>
#include <string_view>
void help() {
std::cout << "Usage: hyprlock [options]\n\n"
std::println("Usage: hyprlock [options]\n\n"
"Options:\n"
" -v, --verbose - Enable verbose logging\n"
" -q, --quiet - Disable logging\n"
" -c FILE, --config FILE - Specify config file to use\n"
" --display (display) - Specify the Wayland display to connect to\n"
" --display NAME - Specify the Wayland display to connect to\n"
" --immediate - Lock immediately, ignoring any configured grace period\n"
" -h, --help - Show this help message\n";
" --immediate-render - Do not wait for resources before drawing the background\n"
" --no-fade-in - Disable the fade-in animation when the lock screen appears\n"
" -V, --version - Show version information\n"
" -h, --help - Show this help message");
}
std::optional<std::string> parseArg(const std::vector<std::string>& args, const std::string& flag, std::size_t& i) {
if (i + 1 < args.size()) {
return args[++i];
} else {
std::cerr << "Error: Missing value for " << flag << " option.\n";
std::println(stderr, "Error: Missing value for {} option.", flag);
return std::nullopt;
}
}
static void printVersion() {
constexpr bool ISTAGGEDRELEASE = std::string_view(HYPRLOCK_COMMIT) == HYPRLOCK_VERSION_COMMIT;
if (ISTAGGEDRELEASE)
std::println("Hyprlock version v{}", HYPRLOCK_VERSION);
else
std::println("Hyprlock version v{} (commit {})", HYPRLOCK_VERSION, HYPRLOCK_COMMIT);
}
int main(int argc, char** argv, char** envp) {
std::string configPath;
std::string wlDisplay;
bool immediate = false;
bool showHelp = false;
bool immediate = false;
bool immediateRender = false;
bool noFadeIn = false;
std::vector<std::string> args(argv, argv + argc);
for (std::size_t i = 1; i < args.size(); ++i) {
const std::string arg = argv[i];
if (arg == "--help" || arg == "-h") {
help();
return 0;
}
if (arg == "--version" || arg == "-V") {
printVersion();
return 0;
}
if (arg == "--verbose" || arg == "-v")
Debug::verbose = true;
@ -55,24 +80,24 @@ int main(int argc, char** argv, char** envp) {
} else if (arg == "--immediate")
immediate = true;
else if (arg == "--help" || arg == "-h") {
showHelp = true;
break;
else if (arg == "--immediate-render")
immediateRender = true;
} else {
std::cerr << "Unknown option: " << arg << "\n";
else if (arg == "--no-fade-in")
noFadeIn = true;
else {
std::println(stderr, "Unknown option: {}", arg);
help();
return 1;
}
}
if (showHelp) {
help();
return 0;
}
printVersion();
g_pAnimationManager = makeUnique<CHyprlockAnimationManager>();
try {
g_pConfigManager = std::make_unique<CConfigManager>(configPath);
g_pConfigManager = makeUnique<CConfigManager>(configPath);
g_pConfigManager->init();
} catch (const std::exception& ex) {
Debug::log(CRIT, "ConfigManager threw: {}", ex.what());
@ -82,8 +107,11 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
if (noFadeIn || immediate)
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate);
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediate, immediateRender);
g_pHyprlock->run();
} catch (const std::exception& ex) {
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());

View file

@ -2,16 +2,25 @@
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include <cairo/cairo.h>
#include <magic.h>
#include <pango/pangocairo.h>
#include <algorithm>
#include <filesystem>
#include "../core/hyprlock.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Jpeg.hpp"
#include "../helpers/Webp.hpp"
#include "src/helpers/Color.hpp"
#include "src/helpers/Log.hpp"
#include <hyprgraphics/image/Image.hpp>
using namespace Hyprgraphics;
CAsyncResourceGatherer::CAsyncResourceGatherer() {
if (g_pHyprlock->getScreencopy())
enqueueScreencopyFrames();
initialGatherThread = std::thread([this]() { this->gather(); });
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
}
void CAsyncResourceGatherer::enqueueScreencopyFrames() {
// some things can't be done async :(
// gather background textures when needed
@ -38,22 +47,13 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() {
}
for (auto& mon : mons) {
const auto MON = std::find_if(g_pHyprlock->m_vOutputs.begin(), g_pHyprlock->m_vOutputs.end(),
[mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
const auto MON = std::ranges::find_if(g_pHyprlock->m_vOutputs, [mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
if (MON == g_pHyprlock->m_vOutputs.end())
continue;
const auto PMONITOR = MON->get();
dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR));
scframes.emplace_back(makeUnique<CScreencopyFrame>(*MON));
}
asyncLoopThread = std::thread([this]() {
this->gather(); /* inital gather */
this->asyncAssetSpinLock();
});
asyncLoopThread.detach();
}
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
@ -69,20 +69,24 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
}
};
for (auto& dma : dmas) {
if (id == dma->resourceID)
return dma->asset.ready ? &dma->asset : nullptr;
for (auto& frame : scframes) {
if (id == frame->m_resourceID)
return frame->m_asset.ready ? &frame->m_asset : nullptr;
}
return nullptr;
}
enum class FileType {
PNG,
JPEG,
WEBP,
UNKNOWN,
};
static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) {
auto image = CImage(path);
if (!image.success()) {
Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError());
return nullptr;
}
return image.cairoSurface();
}
void CAsyncResourceGatherer::gather() {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
@ -110,72 +114,23 @@ void CAsyncResourceGatherer::gather() {
if (path.empty() || path == "screenshot")
continue;
std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path;
std::filesystem::path ABSOLUTEPATH(absolutePath(path, ""));
std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path;
// determine the file type
std::string ext = ABSOLUTEPATH.extension().string();
// convert the extension to lower case
std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { return c <= 'Z' && c >= 'A' ? c - ('Z' - 'z') : c; });
// render the image directly, since we are in a seperate thread
CAsyncResourceGatherer::SPreloadRequest rq;
rq.type = CAsyncResourceGatherer::TARGET_IMAGE;
rq.asset = path;
rq.id = id;
FileType ft = FileType::UNKNOWN;
Debug::log(WARN, "Extension: {}", ext);
if (ext == ".png")
ft = FileType::PNG;
else if (ext == ".jpg" || ext == ".jpeg")
ft = FileType::JPEG;
else if (ext == ".webp")
ft = FileType::WEBP;
else {
// magic is slow, so only use it when no recognized extension is found
auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
magic_load(handle, nullptr);
const auto type_str = std::string(magic_file(handle, ABSOLUTEPATH.c_str()));
const auto first_word = type_str.substr(0, type_str.find(" "));
magic_close(handle);
if (first_word == "PNG")
ft = FileType::PNG;
else if (first_word == "JPEG")
ft = FileType::JPEG;
else if (first_word == "RIFF" && type_str.find("Web/P image") != std::string::npos)
ft = FileType::WEBP;
}
// preload bg img
cairo_surface_t* CAIROISURFACE = nullptr;
switch (ft) {
case FileType::PNG: CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str()); break;
case FileType::JPEG: CAIROISURFACE = JPEG::createSurfaceFromJPEG(ABSOLUTEPATH); break;
case FileType::WEBP: CAIROISURFACE = WEBP::createSurfaceFromWEBP(ABSOLUTEPATH); break;
default: Debug::log(ERR, "unrecognized image format of {}", path.c_str()); continue;
}
if (CAIROISURFACE == nullptr)
continue;
const auto CAIRO = cairo_create(CAIROISURFACE);
cairo_scale(CAIRO, 1, 1);
const auto TARGET = &preloadTargets.emplace_back(CAsyncResourceGatherer::SPreloadTarget{});
TARGET->size = {cairo_image_surface_get_width(CAIROISURFACE), cairo_image_surface_get_height(CAIROISURFACE)};
TARGET->type = TARGET_IMAGE;
TARGET->id = id;
const auto DATA = cairo_image_surface_get_data(CAIROISURFACE);
TARGET->cairo = CAIRO;
TARGET->cairosurface = CAIROISURFACE;
TARGET->data = DATA;
renderImage(rq);
}
}
while (std::any_of(dmas.begin(), dmas.end(), [](const auto& d) { return !d->asset.ready; })) {
while (!g_pHyprlock->m_bTerminate && std::ranges::any_of(scframes, [](const auto& d) { return !d->m_asset.ready; })) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
ready = true;
gathered = true;
}
bool CAsyncResourceGatherer::apply() {
@ -192,13 +147,13 @@ bool CAsyncResourceGatherer::apply() {
for (auto& t : currentPreloadTargets) {
if (t.type == TARGET_IMAGE) {
const auto ASSET = &assets[t.id];
const auto ASSET = &assets[t.id];
const auto SURFACESTATUS = cairo_surface_status((cairo_surface_t*)t.cairosurface);
const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface);
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
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));
@ -218,14 +173,11 @@ bool CAsyncResourceGatherer::apply() {
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
cairo_destroy((cairo_t*)t.cairo);
cairo_surface_destroy((cairo_surface_t*)t.cairosurface);
} else {
Debug::log(ERR, "Unsupported type in ::apply() {}", (int)t.type);
}
t.cairosurface.reset();
} else
Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type);
}
applied = true;
return true;
}
@ -234,16 +186,21 @@ void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
target.type = TARGET_IMAGE;
target.id = rq.id;
const auto ABSOLUTEPATH = absolutePath(rq.asset, "");
const auto CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str());
std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, ""));
const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH);
const auto CAIRO = cairo_create(CAIROISURFACE);
if (!CAIROISURFACE) {
Debug::log(ERR, "renderImage: No cairo surface!");
return;
}
const auto CAIRO = cairo_create(CAIROISURFACE->cairo());
cairo_scale(CAIRO, 1, 1);
target.cairo = CAIRO;
target.cairosurface = CAIROISURFACE;
target.data = cairo_image_surface_get_data(CAIROISURFACE);
target.size = {(double)cairo_image_surface_get_width(CAIROISURFACE), (double)cairo_image_surface_get_height(CAIROISURFACE)};
target.data = CAIROISURFACE->data();
target.size = CAIROISURFACE->size();
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
@ -254,21 +211,21 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
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;
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0);
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans";
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
static auto* const TRIM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:text_trim");
std::string TEXT = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;
if (**TRIM) {
TEXT.erase(0, TEXT.find_first_not_of(" \n\r\t"));
TEXT.erase(TEXT.find_last_not_of(" \n\r\t") + 1);
if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));
text.erase(text.find_last_not_of(" \n\r\t") + 1);
}
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */);
auto CAIRO = cairo_create(CAIROSURFACE);
auto CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */));
auto CAIRO = cairo_create(CAIROSURFACE->cairo());
// draw title using Pango
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
@ -292,12 +249,12 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
PangoAttrList* attrList = nullptr;
GError* gError = nullptr;
char* buf = nullptr;
if (pango_parse_markup(TEXT.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
if (pango_parse_markup(text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
pango_layout_set_text(layout, buf, -1);
else {
Debug::log(ERR, "Pango markup parsing for {} failed: {}", TEXT, gError->message);
Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message);
g_error_free(gError);
pango_layout_set_text(layout, TEXT.c_str(), -1);
pango_layout_set_text(layout, text.c_str(), -1);
}
if (!attrList)
@ -315,9 +272,8 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
// TODO: avoid this?
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE);
CAIRO = cairo_create(CAIROSURFACE);
CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE));
CAIRO = cairo_create(CAIROSURFACE->cairo());
// clear the pixmap
cairo_save(CAIRO);
@ -333,33 +289,22 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
g_object_unref(layout);
cairo_surface_flush(CAIROSURFACE);
cairo_surface_flush(CAIROSURFACE->cairo());
target.cairo = CAIRO;
target.cairosurface = CAIROSURFACE;
target.data = cairo_image_surface_get_data(CAIROSURFACE);
target.size = {layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE};
target.data = CAIROSURFACE->data();
target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE};
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
}
struct STimerCallbackData {
void (*cb)(void*) = nullptr;
void* data = nullptr;
};
static void timerCallback(std::shared_ptr<CTimer> self, void* data_) {
auto data = (STimerCallbackData*)data_;
data->cb(data->data);
delete data;
}
void CAsyncResourceGatherer::asyncAssetSpinLock() {
while (!g_pHyprlock->m_bTerminate) {
std::unique_lock lk(asyncLoopState.requestsMutex);
if (asyncLoopState.pending == false) // avoid a lock if a thread managed to request something already since we .unlock()ed
if (!asyncLoopState.pending) // avoid a lock if a thread managed to request something already since we .unlock()ed
asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events
asyncLoopState.pending = false;
@ -376,6 +321,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
// process requests
for (auto& r : requests) {
Debug::log(TRACE, "Processing requested resourceID {}", r.id);
if (r.type == TARGET_TEXT) {
renderText(r);
} else if (r.type == TARGET_IMAGE) {
@ -387,14 +334,14 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
// plant timer for callback
if (r.callback)
g_pHyprlock->addTimer(std::chrono::milliseconds(0), timerCallback, new STimerCallbackData{r.callback, r.callbackData});
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr);
}
}
dmas.clear();
}
void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) {
Debug::log(TRACE, "Requesting label resource {}", request.id);
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
asyncLoopState.requests.push_back(request);
asyncLoopState.pending = true;
@ -407,11 +354,14 @@ void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* asset) {
void CAsyncResourceGatherer::notify() {
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
asyncLoopState.requests.clear();
asyncLoopState.pending = true;
asyncLoopState.requestsCV.notify_all();
}
void CAsyncResourceGatherer::await() {
if (initialGatherThread.joinable())
initialGatherThread.join();
if (asyncLoopThread.joinable())
asyncLoopThread.join();
}

View file

@ -1,10 +1,6 @@
#pragma once
#include "Shader.hpp"
#include "../helpers/Box.hpp"
#include "../helpers/Color.hpp"
#include "DMAFrame.hpp"
#include "Texture.hpp"
#include "Screencopy.hpp"
#include <thread>
#include <atomic>
#include <vector>
@ -12,12 +8,12 @@
#include <condition_variable>
#include <any>
#include "Shared.hpp"
#include <hyprgraphics/cairo/CairoSurface.hpp>
class CAsyncResourceGatherer {
public:
CAsyncResourceGatherer();
std::atomic<bool> ready = false;
std::atomic<bool> applied = false;
std::atomic<bool> gathered = false;
std::atomic<float> progress = 0;
@ -41,8 +37,7 @@ class CAsyncResourceGatherer {
// optional. Callbacks will be dispatched from the main thread,
// so wayland/gl calls are OK.
// will fire once the resource is fully loaded and ready.
void (*callback)(void*) = nullptr;
void* callbackData = nullptr;
std::function<void()> callback = nullptr;
};
void requestAsyncAssetPreload(const SPreloadRequest& request);
@ -52,6 +47,7 @@ class CAsyncResourceGatherer {
private:
std::thread asyncLoopThread;
std::thread initialGatherThread;
void asyncAssetSpinLock();
void renderText(const SPreloadRequest& rq);
@ -68,17 +64,17 @@ class CAsyncResourceGatherer {
} asyncLoopState;
struct SPreloadTarget {
eTargetType type = TARGET_IMAGE;
std::string id = "";
eTargetType type = TARGET_IMAGE;
std::string id = "";
void* data;
void* cairo;
void* cairosurface;
void* data = nullptr;
void* cairo = nullptr;
SP<Hyprgraphics::CCairoSurface> cairosurface;
Vector2D size;
Vector2D size;
};
std::vector<std::unique_ptr<CDMAFrame>> dmas;
std::vector<UP<CScreencopyFrame>> scframes;
std::vector<SPreloadTarget> preloadTargets;
std::mutex preloadTargetsMutex;
@ -86,4 +82,5 @@ class CAsyncResourceGatherer {
std::unordered_map<std::string, SPreloadedAsset> assets;
void gather();
void enqueueScreencopyFrames();
};

View file

@ -1,253 +0,0 @@
#include "DMAFrame.hpp"
#include "wlr-screencopy-unstable-v1-protocol.h"
#include "../helpers/Log.hpp"
#include "../core/hyprlock.hpp"
#include "../core/Egl.hpp"
#include <EGL/eglext.h>
#include <libdrm/drm_fourcc.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <unistd.h>
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
//
static void wlrOnBuffer(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
const auto PDATA = (SScreencopyData*)data;
Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PDATA);
PDATA->size = stride * height;
PDATA->stride = stride;
}
static void wlrOnFlags(void* data, zwlr_screencopy_frame_v1* frame, uint32_t flags) {
;
}
static void wlrOnReady(void* data, zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
const auto PDATA = (SScreencopyData*)data;
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PDATA);
if (!PDATA->frame->onBufferReady()) {
Debug::log(ERR, "onBufferReady failed");
return;
}
zwlr_screencopy_frame_v1_destroy(frame);
}
static void wlrOnFailed(void* data, zwlr_screencopy_frame_v1* frame) {
;
}
static void wlrOnDamage(void* data, zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
;
}
static void wlrOnDmabuf(void* data, zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
const auto PDATA = (SScreencopyData*)data;
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PDATA);
PDATA->w = width;
PDATA->h = height;
PDATA->fmt = format;
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
}
static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) {
const auto PDATA = (SScreencopyData*)data;
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PDATA);
if (!PDATA->frame->onBufferDone()) {
Debug::log(ERR, "onBufferDone failed");
return;
}
zwlr_screencopy_frame_v1_copy(frame, PDATA->frame->wlBuffer);
Debug::log(TRACE, "[sc] wlr frame copied");
}
static const zwlr_screencopy_frame_v1_listener wlrFrameListener = {
.buffer = wlrOnBuffer,
.flags = wlrOnFlags,
.ready = wlrOnReady,
.failed = wlrOnFailed,
.damage = wlrOnDamage,
.linux_dmabuf = wlrOnDmabuf,
.buffer_done = wlrOnBufferDone,
};
std::string CDMAFrame::getResourceId(COutput* output) {
return std::format("dma:{}-{}x{}", output->stringPort, output->size.x, output->size.y);
}
CDMAFrame::CDMAFrame(COutput* output_) {
resourceID = getResourceId(output_);
if (!glEGLImageTargetTexture2DOES) {
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!glEGLImageTargetTexture2DOES) {
Debug::log(ERR, "No glEGLImageTargetTexture2DOES??");
return;
}
}
if (!eglQueryDmaBufModifiersEXT)
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
// firstly, plant a listener for the frame
frameCb = zwlr_screencopy_manager_v1_capture_output(g_pHyprlock->getScreencopy(), false, output_->output);
scdata.frame = this;
zwlr_screencopy_frame_v1_add_listener(frameCb, &wlrFrameListener, &scdata);
}
CDMAFrame::~CDMAFrame() {
if (g_pEGL)
eglDestroyImage(g_pEGL->eglDisplay, image);
// leaks bo and stuff but lives throughout so for now who cares
}
bool CDMAFrame::onBufferDone() {
uint32_t flags = GBM_BO_USE_RENDERING;
if (!eglQueryDmaBufModifiersEXT) {
Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support");
bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags);
} else {
std::vector<uint64_t> mods;
mods.resize(64);
std::vector<EGLBoolean> externalOnly;
externalOnly.resize(64);
int num = 0;
if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, scdata.fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) {
Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo");
bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, flags);
} else {
Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num);
std::vector<uint64_t> goodMods;
for (int i = 0; i < num; ++i) {
if (externalOnly[i]) {
Debug::log(TRACE, "Modifier {:x} failed test", mods[i]);
continue;
}
Debug::log(TRACE, "Modifier {:x} passed test", mods[i]);
goodMods.push_back(mods[i]);
}
uint64_t zero = 0;
bool hasLinear = std::find(goodMods.begin(), goodMods.end(), 0) != goodMods.end();
bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, scdata.w, scdata.h, scdata.fmt, hasLinear ? &zero : goodMods.data(), hasLinear ? 1 : goodMods.size(),
flags);
}
}
if (!bo) {
Debug::log(ERR, "Couldn't create a drm buffer");
return false;
}
planes = gbm_bo_get_plane_count(bo);
uint64_t mod = gbm_bo_get_modifier(bo);
Debug::log(LOG, "bo chose modifier {:x}", mod);
zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params((zwp_linux_dmabuf_v1*)g_pHyprlock->dma.linuxDmabuf);
if (!params) {
Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed");
gbm_bo_destroy(bo);
return false;
}
for (size_t plane = 0; plane < (size_t)planes; plane++) {
size[plane] = 0;
stride[plane] = gbm_bo_get_stride_for_plane(bo, plane);
offset[plane] = gbm_bo_get_offset(bo, plane);
fd[plane] = gbm_bo_get_fd_for_plane(bo, plane);
if (fd[plane] < 0) {
Debug::log(ERR, "gbm_bo_get_fd_for_plane failed");
zwp_linux_buffer_params_v1_destroy(params);
gbm_bo_destroy(bo);
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
close(fd[plane_tmp]);
}
return false;
}
zwp_linux_buffer_params_v1_add(params, fd[plane], plane, offset[plane], stride[plane], mod >> 32, mod & 0xffffffff);
}
wlBuffer = zwp_linux_buffer_params_v1_create_immed(params, scdata.w, scdata.h, scdata.fmt, 0);
zwp_linux_buffer_params_v1_destroy(params);
if (!wlBuffer) {
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
gbm_bo_destroy(bo);
for (size_t plane = 0; plane < (size_t)planes; plane++)
close(fd[plane]);
return false;
}
return true;
}
bool CDMAFrame::onBufferReady() {
static const int general_attribs = 3;
static const int plane_attribs = 5;
static const int entries_per_attrib = 2;
EGLAttrib attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1];
int attr = 0;
Vector2D size{scdata.w, scdata.h};
attribs[attr++] = EGL_WIDTH;
attribs[attr++] = size.x;
attribs[attr++] = EGL_HEIGHT;
attribs[attr++] = size.y;
attribs[attr++] = EGL_LINUX_DRM_FOURCC_EXT;
attribs[attr++] = scdata.fmt;
attribs[attr++] = EGL_DMA_BUF_PLANE0_FD_EXT;
attribs[attr++] = fd[0];
attribs[attr++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
attribs[attr++] = offset[0];
attribs[attr++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
attribs[attr++] = stride[0];
attribs[attr] = EGL_NONE;
image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
if (image == EGL_NO_IMAGE) {
Debug::log(ERR, "failed creating an egl image");
return false;
}
asset.texture.allocate();
asset.texture.m_vSize = size;
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "Got dma frame with size {}", size);
asset.ready = true;
return true;
}

View file

@ -1,48 +0,0 @@
#pragma once
#include "../core/Output.hpp"
#include <gbm.h>
#include "Texture.hpp"
#include "Shared.hpp"
struct zwlr_screencopy_frame_v1;
class CDMAFrame;
struct SScreencopyData {
int w = 0, h = 0;
uint32_t fmt;
size_t size;
size_t stride;
CDMAFrame* frame = nullptr;
};
class CDMAFrame {
public:
static std::string getResourceId(COutput* output);
CDMAFrame(COutput* mon);
~CDMAFrame();
bool onBufferDone();
bool onBufferReady();
wl_buffer* wlBuffer = nullptr;
std::string resourceID;
SPreloadedAsset asset;
private:
gbm_bo* bo = nullptr;
int planes = 0;
int fd[4];
uint32_t size[4], stride[4], offset[4];
zwlr_screencopy_frame_v1* frameCb = nullptr;
SScreencopyData scdata;
EGLImage image = nullptr;
};

View file

@ -41,14 +41,14 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
if (firstAlloc || m_vSize != Vector2D(w, h)) {
glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, 0);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0);
if (m_pStencilTex) {
glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
@ -61,7 +61,7 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
abort();
}
Debug::log(LOG, "Framebuffer created, status {}", status);
Debug::log(TRACE, "Framebuffer created, status {}", status);
}
glBindTexture(GL_TEXTURE_2D, 0);
@ -73,15 +73,20 @@ bool CFramebuffer::alloc(int w, int h, bool highres) {
}
void CFramebuffer::addStencil() {
if (!m_pStencilTex) {
Debug::log(ERR, "No stencil texture allocated.");
return;
}
glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, m_iFb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0);
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo!", status);
RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo! (FB status: {})", status);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@ -99,15 +104,19 @@ void CFramebuffer::release() {
if (m_cTex.m_iTexID)
glDeleteTextures(1, &m_cTex.m_iTexID);
if (m_pStencilTex && m_pStencilTex->m_iTexID)
glDeleteTextures(1, &m_pStencilTex->m_iTexID);
m_cTex.m_iTexID = 0;
m_iFb = -1;
m_vSize = Vector2D();
m_pStencilTex = nullptr;
}
CFramebuffer::~CFramebuffer() {
release();
}
bool CFramebuffer::isAllocated() {
bool CFramebuffer::isAllocated() const {
return m_iFb != (GLuint)-1;
}

View file

@ -1,6 +1,6 @@
#pragma once
#include "../helpers/Vector2D.hpp"
#include "../helpers/Math.hpp"
#include <GLES3/gl32.h>
#include "Texture.hpp"
@ -13,7 +13,7 @@ class CFramebuffer {
void bind() const;
void release();
void reset();
bool isAllocated();
bool isAllocated() const;
Vector2D m_vSize;

View file

@ -1,19 +1,17 @@
#include "Renderer.hpp"
#include "../core/Egl.hpp"
#include "Shaders.hpp"
#include "Screencopy.hpp"
#include "../config/ConfigManager.hpp"
#include "../helpers/Color.hpp"
#include "../core/AnimationManager.hpp"
#include "../core/Egl.hpp"
#include "../core/Output.hpp"
#include "../core/hyprlock.hpp"
#include "../renderer/DMAFrame.hpp"
#include "mtx.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
#include <algorithm>
#include "Shaders.hpp"
#include "widgets/PasswordInputField.hpp"
#include "widgets/Background.hpp"
#include "widgets/Label.hpp"
@ -32,7 +30,7 @@ GLuint compileShader(const GLuint& type, std::string src) {
auto shaderSource = src.c_str();
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
glShaderSource(shader, 1, &shaderSource, nullptr);
glCompileShader(shader);
GLint ok;
@ -46,11 +44,11 @@ GLuint compileShader(const GLuint& type, std::string src) {
GLuint createProgram(const std::string& vert, const std::string& frag) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert.c_str());
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert);
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag);
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag.c_str());
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag);
auto prog = glCreateProgram();
glAttachShader(prog, vertCompiled);
@ -80,7 +78,7 @@ CRenderer::CRenderer() {
g_pEGL->makeCurrent(nullptr);
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(glMessageCallbackA, 0);
glDebugMessageCallback(glMessageCallbackA, nullptr);
GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC);
rectShader.program = prog;
@ -110,6 +108,27 @@ CRenderer::CRenderer() {
texShader.tint = glGetUniformLocation(prog, "tint");
texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
prog = createProgram(TEXVERTSRC, TEXMIXFRAGSRCRGBA);
texMixShader.program = prog;
texMixShader.proj = glGetUniformLocation(prog, "proj");
texMixShader.tex = glGetUniformLocation(prog, "tex1");
texMixShader.tex2 = glGetUniformLocation(prog, "tex2");
texMixShader.alphaMatte = glGetUniformLocation(prog, "texMatte");
texMixShader.alpha = glGetUniformLocation(prog, "alpha");
texMixShader.mixFactor = glGetUniformLocation(prog, "mixFactor");
texMixShader.texAttrib = glGetAttribLocation(prog, "texcoord");
texMixShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
texMixShader.posAttrib = glGetAttribLocation(prog, "pos");
texMixShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
texMixShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
texMixShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
texMixShader.topLeft = glGetUniformLocation(prog, "topLeft");
texMixShader.fullSize = glGetUniformLocation(prog, "fullSize");
texMixShader.radius = glGetUniformLocation(prog, "radius");
texMixShader.applyTint = glGetUniformLocation(prog, "applyTint");
texMixShader.tint = glGetUniformLocation(prog, "tint");
texMixShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
prog = createProgram(TEXVERTSRC, FRAGBLUR1);
blurShader1.program = prog;
blurShader1.tex = glGetUniformLocation(prog, "tex");
@ -154,20 +173,35 @@ CRenderer::CRenderer() {
blurFinishShader.colorizeTint = glGetUniformLocation(prog, "colorizeTint");
blurFinishShader.boostA = glGetUniformLocation(prog, "boostA");
wlr_matrix_identity(projMatrix.data());
prog = createProgram(QUADVERTSRC, FRAGBORDER);
borderShader.program = prog;
borderShader.proj = glGetUniformLocation(prog, "proj");
borderShader.thick = glGetUniformLocation(prog, "thick");
borderShader.posAttrib = glGetAttribLocation(prog, "pos");
borderShader.texAttrib = glGetAttribLocation(prog, "texcoord");
borderShader.topLeft = glGetUniformLocation(prog, "topLeft");
borderShader.bottomRight = glGetUniformLocation(prog, "bottomRight");
borderShader.fullSize = glGetUniformLocation(prog, "fullSize");
borderShader.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed");
borderShader.radius = glGetUniformLocation(prog, "radius");
borderShader.radiusOuter = glGetUniformLocation(prog, "radiusOuter");
borderShader.gradient = glGetUniformLocation(prog, "gradient");
borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength");
borderShader.angle = glGetUniformLocation(prog, "angle");
borderShader.gradient2 = glGetUniformLocation(prog, "gradient2");
borderShader.gradient2Length = glGetUniformLocation(prog, "gradient2Length");
borderShader.angle2 = glGetUniformLocation(prog, "angle2");
borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp");
borderShader.alpha = glGetUniformLocation(prog, "alpha");
asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>();
asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>();
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
}
static int frames = 0;
//
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar");
static auto* const PNOFADEIN = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_in");
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
matrixProjection(projection.data(), surf.size.x, surf.size.y, WL_OUTPUT_TRANSFORM_NORMAL);
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
g_pEGL->makeCurrent(surf.eglSurface);
glViewport(0, 0, surf.size.x, surf.size.y);
@ -183,67 +217,37 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SRenderFeedback feedback;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
float bga = asyncResourceGatherer->applied ?
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - gatheredAt).count() / 500000.0, 0.0, 1.0) :
0.0;
if (!asyncResourceGatherer->ready) {
// render status
if (!**PDISABLEBAR) {
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
}
} else {
if (!asyncResourceGatherer->applied) {
asyncResourceGatherer->apply();
gatheredAt = std::chrono::system_clock::now();
}
if (**PNOFADEIN)
bga = 1.0;
if (g_pHyprlock->m_bFadeStarted && !**PNOFADEOUT) {
bga =
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(g_pHyprlock->m_tFadeEnds - std::chrono::system_clock::now()).count() / 500000.0 - 0.02, 0.0, 1.0);
// - 0.02 so that the fade ends a little earlier than the final second
}
if (!WAITFORASSETS) {
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
for (auto& w : *WIDGETS) {
feedback.needsFrame = w->draw({bga}) || feedback.needsFrame;
const auto WIDGETS = getOrCreateWidgetsFor(surf);
for (auto& w : WIDGETS) {
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
}
frames++;
Debug::log(TRACE, "frame {}", frames);
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->ready || bga < 1.0;
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered;
glDisable(GL_BLEND);
return feedback;
}
void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
float matrix[9];
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, box.rot,
projMatrix.data()); // TODO: write own, don't use WLR here
float glMatrix[9];
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
void CRenderer::renderRect(const CBox& box, const CHyprColor& col, int rounding) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
glUseProgram(rectShader.program);
glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix);
glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
// premultiply the color as well as we don't work with straight alpha
glUniform4f(rectShader.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a);
const auto TOPLEFT = Vector2D(box.x, box.y);
const auto FULLSIZE = Vector2D(box.width, box.height);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(rectShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
@ -259,26 +263,60 @@ void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
glDisableVertexAttribArray(rectShader.posAttrib);
}
void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional<wl_output_transform> tr) {
float matrix[9];
wlr_matrix_project_box(matrix, &box, tr.value_or(WL_OUTPUT_TRANSFORM_FLIPPED_180) /* ugh coordinate spaces */, box.rot,
projMatrix.data()); // TODO: write own, don't use WLR here
void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding, float alpha) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
float glMatrix[9];
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
glUseProgram(borderShader.program);
CShader* shader = &texShader;
glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4fv(borderShader.gradient, gradient.m_vColorsOkLabA.size() / 4, (float*)gradient.m_vColorsOkLabA.data());
glUniform1i(borderShader.gradientLength, gradient.m_vColorsOkLabA.size() / 4);
glUniform1f(borderShader.angle, (int)(gradient.m_fAngle / (M_PI / 180.0)) % 360 * (M_PI / 180.0));
glUniform1f(borderShader.alpha, alpha);
glUniform1i(borderShader.gradient2Length, 0);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
glUniform2f(borderShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(borderShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform2f(borderShader.fullSizeUntransformed, (float)box.width, (float)box.height);
glUniform1f(borderShader.radius, rounding);
glUniform1f(borderShader.radiusOuter, rounding);
glUniform1f(borderShader.thick, thickness);
glVertexAttribPointer(borderShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(borderShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(borderShader.posAttrib);
glEnableVertexAttribArray(borderShader.texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(borderShader.posAttrib);
glDisableVertexAttribArray(borderShader.texAttrib);
}
void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional<eTransform> tr) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CShader* shader = &texShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex.m_iTarget, tex.m_iTexID);
glUseProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1i(shader->tex, 0);
glUniform1f(shader->alpha, a);
const auto TOPLEFT = Vector2D(box.x, box.y);
const auto FULLSIZE = Vector2D(box.width, box.height);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
@ -303,68 +341,105 @@ void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int
glBindTexture(tex.m_iTarget, 0);
}
std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) {
if (!widgets.contains(surf)) {
void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a, float mixFactor, int rounding, std::optional<eTransform> tr) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CShader* shader = &texMixShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex.m_iTarget, tex.m_iTexID);
glActiveTexture(GL_TEXTURE1);
glBindTexture(tex2.m_iTarget, tex2.m_iTexID);
glUseProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1i(shader->tex, 0);
glUniform1i(shader->tex2, 1);
glUniform1f(shader->alpha, a);
glUniform1f(shader->mixFactor, mixFactor);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
glUniform1f(shader->radius, rounding);
glUniform1i(shader->discardOpaque, 0);
glUniform1i(shader->discardAlpha, 0);
glUniform1i(shader->applyTint, 0);
glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(shader->posAttrib);
glEnableVertexAttribArray(shader->texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(shader->posAttrib);
glDisableVertexAttribArray(shader->texAttrib);
glBindTexture(tex.m_iTarget, 0);
}
template <class Widget>
static void createWidget(std::vector<SP<IWidget>>& widgets) {
const auto W = makeShared<Widget>();
W->registerSelf(W);
widgets.emplace_back(W);
}
std::vector<SP<IWidget>>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface& surf) {
RASSERT(surf.m_outputID != OUTPUT_INVALID, "Invalid output ID!");
if (!widgets.contains(surf.m_outputID)) {
auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
std::sort(CWIDGETS.begin(), CWIDGETS.end(), [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) {
std::ranges::sort(CWIDGETS, [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) {
return std::any_cast<Hyprlang::INT>(a.values.at("zindex")) < std::any_cast<Hyprlang::INT>(b.values.at("zindex"));
});
const auto POUTPUT = surf.m_outputRef.lock();
for (auto& c : CWIDGETS) {
if (!c.monitor.empty() && c.monitor != surf->output->stringPort && !surf->output->stringDesc.starts_with(c.monitor))
if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !("desc:" + POUTPUT->stringDesc).starts_with(c.monitor))
continue;
// by type
if (c.type == "background") {
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
std::string resourceID = "";
if (PATH == "screenshot") {
resourceID = CDMAFrame::getResourceId(surf->output);
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (asyncResourceGatherer->ready) {
if (!asyncResourceGatherer->getAssetByID(resourceID))
resourceID = ""; // Fallback to solid color (background:color)
}
} else if (!PATH.empty())
resourceID = "background:" + PATH;
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, surf->output, resourceID, c.values, PATH == "screenshot"));
createWidget<CBackground>(widgets[surf.m_outputID]);
} else if (c.type == "input-field") {
widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(surf->size, c.values, surf->output->stringPort));
createWidget<CPasswordInputField>(widgets[surf.m_outputID]);
} else if (c.type == "label") {
widgets[surf].emplace_back(std::make_unique<CLabel>(surf->size, c.values, surf->output->stringPort));
createWidget<CLabel>(widgets[surf.m_outputID]);
} else if (c.type == "shape") {
widgets[surf].emplace_back(std::make_unique<CShape>(surf->size, c.values));
createWidget<CShape>(widgets[surf.m_outputID]);
} else if (c.type == "image") {
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
std::string resourceID = "";
if (!PATH.empty())
resourceID = "image:" + PATH;
widgets[surf].emplace_back(std::make_unique<CImage>(surf->size, surf->output, resourceID, c.values));
createWidget<CImage>(widgets[surf.m_outputID]);
} else {
Debug::log(ERR, "Unknown widget type: {}", c.type);
continue;
}
widgets[surf.m_outputID].back()->configure(c.values, POUTPUT);
}
}
return &widgets[surf];
return widgets[surf.m_outputID];
}
void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
float matrix[9];
CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y};
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
projMatrix.data()); // TODO: write own, don't use WLR here
float glMatrix[9];
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
CBox box{0, 0, outfb.m_vSize.x, outfb.m_vSize.y};
box.round();
Mat3x3 matrix = projMatrix.projectBox(box, HYPRUTILS_TRANSFORM_NORMAL, 0);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
CFramebuffer mirrors[2];
mirrors[0].alloc(outfb.m_vSize.x, outfb.m_vSize.y, true);
@ -385,7 +460,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
glUseProgram(blurPrepareShader.program);
glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix);
glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(blurPrepareShader.contrast, params.contrast);
glUniform1f(blurPrepareShader.brightness, params.brightness);
glUniform1i(blurPrepareShader.tex, 0);
@ -420,7 +495,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
glUseProgram(pShader->program);
// prep two shaders
glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix);
glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(pShader->radius, params.size);
if (pShader == &blurShader1) {
glUniform2f(blurShader1.halfpixel, 0.5f / (outfb.m_vSize.x / 2.f), 0.5f / (outfb.m_vSize.y / 2.f));
@ -476,7 +551,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
glUseProgram(blurFinishShader.program);
glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix);
glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(blurFinishShader.noise, params.noise);
glUniform1f(blurFinishShader.brightness, params.brightness);
glUniform1i(blurFinishShader.colorize, params.colorize.has_value());
@ -505,7 +580,7 @@ void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
// finish
outfb.bind();
renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, WL_OUTPUT_TRANSFORM_NORMAL);
renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, HYPRUTILS_TRANSFORM_NORMAL);
glEnable(GL_BLEND);
}
@ -520,6 +595,30 @@ void CRenderer::popFb() {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundFBs.empty() ? 0 : boundFBs.back());
}
void CRenderer::removeWidgetsFor(const CSessionLockSurface* surf) {
widgets.erase(surf);
void CRenderer::removeWidgetsFor(OUTPUTID id) {
widgets.erase(id);
}
void CRenderer::reconfigureWidgetsFor(OUTPUTID id) {
// TODO: reconfigure widgets by just calling their configure method again.
// Requires a way to get a widgets config properties.
// I think the best way would be to store the anonymos key of the widget config.
removeWidgetsFor(id);
}
void CRenderer::startFadeIn() {
Debug::log(LOG, "Starting fade in");
*opacity = 1.f;
opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true);
}
void CRenderer::startFadeOut(bool unlock, bool immediate) {
if (immediate)
opacity->setValueAndWarp(0.f);
else
*opacity = 0.f;
if (unlock)
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
}

View file

@ -1,18 +1,18 @@
#pragma once
#include <memory>
#include <chrono>
#include <optional>
#include "../core/LockSurface.hpp"
#include "Shader.hpp"
#include "../helpers/Box.hpp"
#include "../defines.hpp"
#include "../core/LockSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Color.hpp"
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigDataValues.hpp"
#include "widgets/IWidget.hpp"
#include "Framebuffer.hpp"
typedef std::unordered_map<const CSessionLockSurface*, std::vector<std::unique_ptr<IWidget>>> widgetMap_t;
typedef std::unordered_map<OUTPUTID, std::vector<SP<IWidget>>> widgetMap_t;
class CRenderer {
public:
@ -23,42 +23,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 renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional<wl_output_transform> tr = {});
void blurFB(const CFramebuffer& outfb, SBlurParams params);
void renderRect(const CBox& box, const CHyprColor& col, int rounding = 0);
void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0);
void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional<eTransform> tr = {});
void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional<eTransform> tr = {});
void blurFB(const CFramebuffer& outfb, SBlurParams params);
std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point gatheredAt;
UP<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point firstFullFrameTime;
void pushFb(GLint fb);
void popFb();
void pushFb(GLint fb);
void popFb();
void removeWidgetsFor(const CSessionLockSurface* surf);
void removeWidgetsFor(OUTPUTID id);
void reconfigureWidgetsFor(OUTPUTID id);
void startFadeIn();
void startFadeOut(bool unlock = false, bool immediate = true);
std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
private:
widgetMap_t widgets;
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 blurShader1;
CShader blurShader2;
CShader blurPrepareShader;
CShader blurFinishShader;
Mat3x3 projMatrix = Mat3x3::identity();
Mat3x3 projection;
std::array<float, 9> projMatrix;
std::array<float, 9> projection;
PHLANIMVAR<float> opacity;
std::vector<GLint> boundFBs;
std::vector<GLint> boundFBs;
};
inline std::unique_ptr<CRenderer> g_pRenderer;
inline UP<CRenderer> g_pRenderer;

475
src/renderer/Screencopy.cpp Normal file
View file

@ -0,0 +1,475 @@
#include "Screencopy.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../core/hyprlock.hpp"
#include "../core/Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <cstring>
#include <array>
#include <cstdint>
#include <gbm.h>
#include <hyprutils/memory/UniquePtr.hpp>
#include <unistd.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <GLES2/gl2ext.h>
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
//
std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) {
return std::format("screencopy:{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y);
}
CScreencopyFrame::CScreencopyFrame(SP<COutput> pOutput) : m_outputRef(pOutput) {
captureOutput();
static const auto SCMODE = g_pConfigManager->getValue<Hyprlang::INT>("general:screencopy_mode");
if (*SCMODE == 1)
m_frame = makeUnique<CSCSHMFrame>(m_sc);
else
m_frame = makeUnique<CSCDMAFrame>(m_sc);
}
void CScreencopyFrame::captureOutput() {
const auto POUTPUT = m_outputRef.lock();
RASSERT(POUTPUT, "Screencopy, but no valid output");
m_resourceID = getResourceId(POUTPUT);
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, POUTPUT->m_wlOutput->resource()));
m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) {
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this);
if (!m_frame || !m_frame->onBufferDone() || !m_frame->m_wlBuffer) {
Debug::log(ERR, "[sc] Failed to create a wayland buffer for the screencopy frame");
return;
}
m_sc->sendCopy(m_frame->m_wlBuffer->resource());
Debug::log(TRACE, "[sc] wlr frame copied");
});
m_sc->setFailed([this](CCZwlrScreencopyFrameV1* r) {
Debug::log(ERR, "[sc] wlrOnFailed for {}", (void*)r);
m_frame.reset();
});
m_sc->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) {
Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)this);
if (!m_frame || !m_frame->onBufferReady(m_asset)) {
Debug::log(ERR, "[sc] Failed to bind the screencopy buffer to a texture");
return;
}
m_sc.reset();
});
}
CSCDMAFrame::CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
if (!glEGLImageTargetTexture2DOES) {
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!glEGLImageTargetTexture2DOES) {
Debug::log(ERR, "No glEGLImageTargetTexture2DOES??");
return;
}
}
if (!eglQueryDmaBufModifiersEXT)
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
m_sc->setLinuxDmabuf([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height) {
Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)this);
m_w = width;
m_h = height;
m_fmt = format;
Debug::log(TRACE, "[sc] DMABUF format reported: {:x}", format);
});
m_sc->setBuffer([](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
; // unused by dma
});
}
CSCDMAFrame::~CSCDMAFrame() {
if (g_pEGL)
eglDestroyImage(g_pEGL->eglDisplay, m_image);
// leaks bo and stuff but lives throughout so for now who cares
}
bool CSCDMAFrame::onBufferDone() {
uint32_t flags = GBM_BO_USE_RENDERING;
if (!eglQueryDmaBufModifiersEXT) {
Debug::log(WARN, "Querying modifiers without eglQueryDmaBufModifiersEXT support");
m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags);
} else {
std::array<uint64_t, 64> mods;
std::array<EGLBoolean, 64> externalOnly;
int num = 0;
if (!eglQueryDmaBufModifiersEXT(g_pEGL->eglDisplay, m_fmt, 64, mods.data(), externalOnly.data(), &num) || num == 0) {
Debug::log(WARN, "eglQueryDmaBufModifiersEXT failed, falling back to regular bo");
m_bo = gbm_bo_create(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, flags);
} else {
Debug::log(LOG, "eglQueryDmaBufModifiersEXT found {} mods", num);
std::vector<uint64_t> goodMods;
for (int i = 0; i < num; ++i) {
if (externalOnly[i]) {
Debug::log(TRACE, "Modifier {:x} failed test", mods[i]);
continue;
}
Debug::log(TRACE, "Modifier {:x} passed test", mods[i]);
goodMods.emplace_back(mods[i]);
}
m_bo = gbm_bo_create_with_modifiers2(g_pHyprlock->dma.gbmDevice, m_w, m_h, m_fmt, goodMods.data(), goodMods.size(), flags);
}
}
if (!m_bo) {
Debug::log(ERR, "[bo] Couldn't create a drm buffer");
return false;
}
m_planes = gbm_bo_get_plane_count(m_bo);
Debug::log(LOG, "[bo] has {} plane(s)", m_planes);
m_mod = gbm_bo_get_modifier(m_bo);
Debug::log(LOG, "[bo] chose modifier {:x}", m_mod);
auto params = makeShared<CCZwpLinuxBufferParamsV1>(g_pHyprlock->dma.linuxDmabuf->sendCreateParams());
if (!params) {
Debug::log(ERR, "zwp_linux_dmabuf_v1_create_params failed");
gbm_bo_destroy(m_bo);
return false;
}
for (size_t plane = 0; plane < (size_t)m_planes; plane++) {
m_stride[plane] = gbm_bo_get_stride_for_plane(m_bo, plane);
m_offset[plane] = gbm_bo_get_offset(m_bo, plane);
m_fd[plane] = gbm_bo_get_fd_for_plane(m_bo, plane);
if (m_fd[plane] < 0) {
Debug::log(ERR, "gbm_m_bo_get_fd_for_plane failed");
params.reset();
gbm_bo_destroy(m_bo);
for (size_t plane_tmp = 0; plane_tmp < plane; plane_tmp++) {
close(m_fd[plane_tmp]);
}
return false;
}
params->sendAdd(m_fd[plane], plane, m_offset[plane], m_stride[plane], m_mod >> 32, m_mod & 0xffffffff);
}
m_wlBuffer = makeShared<CCWlBuffer>(params->sendCreateImmed(m_w, m_h, m_fmt, (zwpLinuxBufferParamsV1Flags)0));
params.reset();
if (!m_wlBuffer) {
Debug::log(ERR, "[pw] zwp_linux_buffer_params_v1_create_immed failed");
gbm_bo_destroy(m_bo);
for (size_t plane = 0; plane < (size_t)m_planes; plane++)
close(m_fd[plane]);
return false;
}
return true;
}
bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) {
static constexpr struct {
EGLAttrib fd;
EGLAttrib offset;
EGLAttrib pitch;
EGLAttrib modlo;
EGLAttrib modhi;
} attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT,
.offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE1_FD_EXT,
.offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE2_FD_EXT,
.offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},
{.fd = EGL_DMA_BUF_PLANE3_FD_EXT,
.offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT,
.pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT,
.modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
.modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}};
std::vector<EGLAttrib> attribs = {
EGL_WIDTH, m_w, EGL_HEIGHT, m_h, EGL_LINUX_DRM_FOURCC_EXT, m_fmt,
};
for (int i = 0; i < m_planes; i++) {
attribs.emplace_back(attrNames[i].fd);
attribs.emplace_back(m_fd[i]);
attribs.emplace_back(attrNames[i].offset);
attribs.emplace_back(m_offset[i]);
attribs.emplace_back(attrNames[i].pitch);
attribs.emplace_back(m_stride[i]);
if (m_mod != DRM_FORMAT_MOD_INVALID) {
attribs.emplace_back(attrNames[i].modlo);
attribs.emplace_back(m_mod & 0xFFFFFFFF);
attribs.emplace_back(attrNames[i].modhi);
attribs.emplace_back(m_mod >> 32);
}
}
attribs.emplace_back(EGL_NONE);
m_image = eglCreateImage(g_pEGL->eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
if (m_image == EGL_NO_IMAGE) {
Debug::log(ERR, "Failed creating an egl image");
return false;
}
asset.texture.allocate();
asset.texture.m_vSize = {m_w, m_h};
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "Got dma frame with size {}", asset.texture.m_vSize);
asset.ready = true;
return true;
}
CSCSHMFrame::CSCSHMFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
Debug::log(TRACE, "[sc] [shm] Creating a SHM frame");
m_sc->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
Debug::log(TRACE, "[sc] [shm] wlrOnBuffer for {}", (void*)this);
const auto SIZE = stride * height;
m_shmFmt = format;
m_w = width;
m_h = height;
m_stride = stride;
// Create a shm pool with format and size
std::string shmPoolFile;
const auto FD = createPoolFile(SIZE, shmPoolFile);
if (FD < 0) {
Debug::log(ERR, "[sc] [shm] failed to create a pool file");
return;
}
m_shmData = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
if (m_shmData == MAP_FAILED) {
Debug::log(ERR, "[sc] [shm] failed to (errno {})", strerror(errno));
close(FD);
m_ok = false;
return;
}
if (!g_pHyprlock->getShm()) {
Debug::log(ERR, "[sc] [shm] Failed to get WLShm global");
close(FD);
m_ok = false;
return;
}
auto pShmPool = makeShared<CCWlShmPool>(g_pHyprlock->getShm()->sendCreatePool(FD, SIZE));
m_wlBuffer = makeShared<CCWlBuffer>(pShmPool->sendCreateBuffer(0, width, height, stride, m_shmFmt));
pShmPool.reset();
close(FD);
});
m_sc->setLinuxDmabuf([](CCZwlrScreencopyFrameV1* r, uint32_t, uint32_t, uint32_t) {
; // unused by scshm
});
}
CSCSHMFrame::~CSCSHMFrame() {
if (m_convBuffer)
free(m_convBuffer);
if (m_shmData)
munmap(m_shmData, m_stride * m_h);
}
void CSCSHMFrame::convertBuffer() {
const auto BYTESPERPX = m_stride / m_w;
if (BYTESPERPX == 4) {
switch (m_shmFmt) {
case WL_SHM_FORMAT_ARGB8888:
case WL_SHM_FORMAT_XRGB8888: {
Debug::log(LOG, "[sc] [shm] Converting ARGB to RGBA");
uint8_t* data = (uint8_t*)m_shmData;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4));
// RGBA
*px = {.blue = px->red, .green = px->green, .red = px->blue, .alpha = px->alpha};
}
}
} break;
case WL_SHM_FORMAT_ABGR8888:
case WL_SHM_FORMAT_XBGR8888: {
Debug::log(LOG, "[sc] [shm] Converting ABGR to RGBA");
uint8_t* data = (uint8_t*)m_shmData;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)(data + (y * m_w * 4) + (x * 4));
// BGRA
*px = {.blue = px->blue, .green = px->green, .red = px->red, .alpha = px->alpha};
}
}
} break;
case WL_SHM_FORMAT_ABGR2101010:
case WL_SHM_FORMAT_ARGB2101010:
case WL_SHM_FORMAT_XRGB2101010:
case WL_SHM_FORMAT_XBGR2101010: {
Debug::log(LOG, "[sc] [shm] Converting 10-bit channels to 8-bit");
uint8_t* data = (uint8_t*)m_shmData;
const bool FLIP = m_shmFmt != WL_SHM_FORMAT_XBGR2101010;
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
uint32_t* px = (uint32_t*)(data + (y * m_w * 4) + (x * 4));
// conv to 8 bit
uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0));
uint8_t G = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000011111111110000000000) >> 10) / 1023.0));
uint8_t B = (uint8_t)std::round((255.0 * (((*px) & 0b00111111111100000000000000000000) >> 20) / 1023.0));
uint8_t A = (uint8_t)std::round((255.0 * (((*px) & 0b11000000000000000000000000000000) >> 30) / 3.0));
// write 8-bit values
*px = ((FLIP ? B : R) << 0) + (G << 8) + ((FLIP ? R : B) << 16) + (A << 24);
}
}
} break;
default: {
Debug::log(WARN, "[sc] [shm] Unsupported format {}", m_shmFmt);
}
}
} else if (BYTESPERPX == 3) {
Debug::log(LOG, "[sc] [shm] Converting 24 bit to 32 bit");
m_convBuffer = malloc(m_w * m_h * 4);
const int NEWSTRIDE = m_w * 4;
RASSERT(m_convBuffer, "malloc failed");
switch (m_shmFmt) {
case WL_SHM_FORMAT_BGR888: {
Debug::log(LOG, "[sc] [shm] Converting BGR to RGBA");
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel3 {
// little-endian RGB
unsigned char blue;
unsigned char green;
unsigned char red;
}* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3));
struct pixel4 {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4));
*dstPx = {.blue = srcPx->blue, .green = srcPx->green, .red = srcPx->red, .alpha = 0xFF};
}
}
} break;
case WL_SHM_FORMAT_RGB888: {
Debug::log(LOG, "[sc] [shm] Converting RGB to RGBA");
for (uint32_t y = 0; y < m_h; ++y) {
for (uint32_t x = 0; x < m_w; ++x) {
struct pixel3 {
// big-endian RGB
unsigned char red;
unsigned char green;
unsigned char blue;
}* srcPx = (struct pixel3*)((char*)m_shmData + (y * m_stride) + (x * 3));
struct pixel4 {
// big-endian ARGB
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
}* dstPx = (struct pixel4*)((char*)m_convBuffer + (y * NEWSTRIDE) + (x * 4));
*dstPx = {.alpha = srcPx->red, .red = srcPx->green, .green = srcPx->blue, .blue = 0xFF};
}
}
} break;
default: {
Debug::log(ERR, "[sc] [shm] Unsupported format for 24bit buffer {}", m_shmFmt);
}
}
} else {
Debug::log(ERR, "[sc] [shm] Unsupported bytes per pixel {}", BYTESPERPX);
}
}
bool CSCSHMFrame::onBufferReady(SPreloadedAsset& asset) {
convertBuffer();
asset.texture.allocate();
asset.texture.m_vSize.x = m_w;
asset.texture.m_vSize.y = m_h;
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
void* buffer = m_convBuffer ? m_convBuffer : m_shmData;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset.texture.m_vSize);
asset.ready = true;
return true;
}

View file

@ -0,0 +1,94 @@
#pragma once
#include "../defines.hpp"
#include "../core/Output.hpp"
#include <cstdint>
#include <gbm.h>
#include <memory>
#include "Shared.hpp"
#include "linux-dmabuf-v1.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
class ISCFrame {
public:
ISCFrame() = default;
virtual ~ISCFrame() = default;
virtual bool onBufferDone() = 0;
virtual bool onBufferReady(SPreloadedAsset& asset) = 0;
SP<CCWlBuffer> m_wlBuffer = nullptr;
};
class CScreencopyFrame {
public:
static std::string getResourceId(SP<COutput> pOutput);
CScreencopyFrame(SP<COutput> pOutput);
~CScreencopyFrame() = default;
void captureOutput();
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
std::string m_resourceID;
SPreloadedAsset m_asset;
private:
WP<COutput> m_outputRef;
UP<ISCFrame> m_frame = nullptr;
bool m_dmaFailed = false;
};
// Uses a gpu buffer created via gbm_bo
class CSCDMAFrame : public ISCFrame {
public:
CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc);
virtual ~CSCDMAFrame();
virtual bool onBufferReady(SPreloadedAsset& asset);
virtual bool onBufferDone();
private:
gbm_bo* m_bo = nullptr;
int m_planes = 0;
uint64_t m_mod = 0;
int m_fd[4];
uint32_t m_stride[4], m_offset[4];
int m_w = 0, m_h = 0;
uint32_t m_fmt = 0;
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
EGLImage m_image = nullptr;
};
// Uses a shm buffer - is slow and needs ugly format conversion
// Used as a fallback just in case.
class CSCSHMFrame : public ISCFrame {
public:
CSCSHMFrame(SP<CCZwlrScreencopyFrameV1> sc);
virtual ~CSCSHMFrame();
virtual bool onBufferDone() {
return m_ok;
}
virtual bool onBufferReady(SPreloadedAsset& asset);
void convertBuffer();
private:
bool m_ok = true;
uint32_t m_w = 0, m_h = 0;
uint32_t m_stride = 0;
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
uint32_t m_shmFmt = 0;
void* m_shmData = nullptr;
void* m_convBuffer = nullptr;
};

View file

@ -13,7 +13,9 @@ class CShader {
GLint color = -1;
GLint alphaMatte = -1;
GLint tex = -1;
GLint tex2 = -1;
GLint alpha = -1;
GLfloat mixFactor = -1;
GLint posAttrib = -1;
GLint texAttrib = -1;
GLint matteTexAttrib = -1;
@ -39,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;

View file

@ -1,6 +1,10 @@
#pragma once
#include <string>
#include <format>
#include <cmath>
constexpr float SHADER_ROUNDED_SMOOTHING_FACTOR = M_PI / 5.34665792551;
inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVarName) -> std::string {
return R"#(
@ -12,17 +16,21 @@ inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVar
pixCoord -= fullSize * 0.5 - radius;
pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left
// smoothing constant for the edge: more = blurrier, but smoother
const float SMOOTHING_CONSTANT = )#" +
std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(;
if (pixCoord.x + pixCoord.y > radius) {
float dist = length(pixCoord);
if (dist > radius + 1.0)
if (dist > radius + SMOOTHING_CONSTANT * 2.0)
discard;
if (dist > radius - 1.0) {
if (dist > radius - SMOOTHING_CONSTANT * 2.0) {
float dist = length(pixCoord);
float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5);
float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
)#" +
colorVarName + R"#( = )#" + colorVarName + R"#( * normalized;
@ -121,6 +129,49 @@ void main() {
gl_FragColor = pixColor * alpha;
})#";
inline const std::string TEXMIXFRAGSRCRGBA = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform float mixFactor;
uniform float alpha;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
uniform int discardOpaque;
uniform int discardAlpha;
uniform float discardAlphaValue;
uniform int applyTint;
uniform vec3 tint;
void main() {
vec4 pixColor = mix(texture2D(tex1, v_texcoord), texture2D(tex2, v_texcoord), smoothstep(0.0, 1.0, mixFactor));
if (discardOpaque == 1 && pixColor[3] * alpha == 1.0)
discard;
if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue)
discard;
if (applyTint == 1) {
pixColor[0] = pixColor[0] * tint[0];
pixColor[1] = pixColor[1] * tint[1];
pixColor[2] = pixColor[2] * tint[2];
}
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor * alpha;
})#";
inline const std::string FRAGBLUR1 = R"#(
#version 100
precision highp float;
@ -362,3 +413,184 @@ void main() {
gl_FragColor = pixColor;
}
)#";
// makes a stencil without corners
inline const std::string FRAGBORDER = R"#(
precision highp float;
varying vec4 v_color;
varying vec2 v_texcoord;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform vec2 fullSizeUntransformed;
uniform float radius;
uniform float radiusOuter;
uniform float thick;
// Gradients are in OkLabA!!!! {l, a, b, alpha}
uniform vec4 gradient[10];
uniform vec4 gradient2[10];
uniform int gradientLength;
uniform int gradient2Length;
uniform float angle;
uniform float angle2;
uniform float gradientLerp;
uniform float alpha;
float linearToGamma(float x) {
return x >= 0.0031308 ? 1.055 * pow(x, 0.416666666) - 0.055 : 12.92 * x;
}
vec4 okLabAToSrgb(vec4 lab) {
float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0);
float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0);
float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0);
return vec4(linearToGamma(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292),
linearToGamma(l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965)),
linearToGamma(l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010),
lab[3]);
}
vec4 getOkColorForCoordArray1(vec2 normalizedCoord) {
if (gradientLength < 2)
return gradient[0];
float finalAng = 0.0;
if (angle > 4.71 /* 270 deg */) {
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = 6.28 - angle;
} else if (angle > 3.14 /* 180 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = angle - 3.14;
} else if (angle > 1.57 /* 90 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
finalAng = 3.14 - angle;
} else {
finalAng = angle;
}
float sine = sin(finalAng);
float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1);
int bottom = int(floor(progress));
int top = bottom + 1;
return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress);
}
vec4 getOkColorForCoordArray2(vec2 normalizedCoord) {
if (gradient2Length < 2)
return gradient2[0];
float finalAng = 0.0;
if (angle2 > 4.71 /* 270 deg */) {
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = 6.28 - angle;
} else if (angle2 > 3.14 /* 180 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = angle - 3.14;
} else if (angle2 > 1.57 /* 90 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
finalAng = 3.14 - angle2;
} else {
finalAng = angle2;
}
float sine = sin(finalAng);
float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1);
int bottom = int(floor(progress));
int top = bottom + 1;
return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress);
}
vec4 getColorForCoord(vec2 normalizedCoord) {
vec4 result1 = getOkColorForCoordArray1(normalizedCoord);
if (gradient2Length <= 0)
return okLabAToSrgb(result1);
vec4 result2 = getOkColorForCoordArray2(normalizedCoord);
return okLabAToSrgb(mix(result1, result2, gradientLerp));
}
void main() {
highp vec2 pixCoord = vec2(gl_FragCoord);
highp vec2 pixCoordOuter = pixCoord;
highp vec2 originalPixCoord = v_texcoord;
originalPixCoord *= fullSizeUntransformed;
float additionalAlpha = 1.0;
vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0);
bool done = false;
pixCoord -= topLeft + fullSize * 0.5;
pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;
pixCoordOuter = pixCoord;
pixCoord -= fullSize * 0.5 - radius;
pixCoordOuter -= fullSize * 0.5 - radiusOuter;
// center the pixes dont make it top-left
pixCoord += vec2(1.0, 1.0) / fullSize;
pixCoordOuter += vec2(1.0, 1.0) / fullSize;
if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) {
// smoothing constant for the edge: more = blurrier, but smoother
const float SMOOTHING_CONSTANT = )#" +
std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(;
float dist = length(pixCoord);
float distOuter = length(pixCoordOuter);
float h = (thick / 2.0);
if (dist < radius - h) {
// lower
float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
additionalAlpha *= normalized;
done = true;
} else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) {
// higher
float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0));
additionalAlpha *= normalized;
done = true;
} else if (distOuter < radiusOuter - h) {
additionalAlpha = 1.0;
done = true;
}
}
// now check for other shit
if (!done) {
// distance to all straight bb borders
float distanceT = originalPixCoord[1];
float distanceB = fullSizeUntransformed[1] - originalPixCoord[1];
float distanceL = originalPixCoord[0];
float distanceR = fullSizeUntransformed[0] - originalPixCoord[0];
// get the smallest
float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR));
if (smallest > thick)
discard;
}
if (additionalAlpha == 0.0)
discard;
pixColor = getColorForCoord(v_texcoord);
pixColor.rgb *= pixColor[3];
pixColor *= alpha * additionalAlpha;
gl_FragColor = pixColor;
}
)#";

View file

@ -1,5 +1,6 @@
#pragma once
#include "Texture.hpp"
#include "../defines.hpp"
struct SPreloadedAsset {
CTexture texture;

View file

@ -1,7 +1,7 @@
#include "Texture.hpp"
CTexture::CTexture() {
// naffin'
; // naffin'
}
CTexture::~CTexture() {

View file

@ -1,7 +1,7 @@
#pragma once
#include <GLES3/gl32.h>
#include "../helpers/Vector2D.hpp"
#include "../helpers/Math.hpp"
enum TEXTURETYPE {
TEXTURE_INVALID, // Invalid

View file

@ -1,35 +1,132 @@
#include "Background.hpp"
#include "../Renderer.hpp"
#include "../mtx.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <chrono>
#include <hyprlang.hpp>
#include <filesystem>
#include <memory>
#include <GLES3/gl32.h>
CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props, bool ss) :
viewport(viewport_), resourceID(resourceID_), output(output_), isScreenshot(ss) {
CBackground::~CBackground() {
reset();
}
color = std::any_cast<Hyprlang::INT>(props.at("color"));
blurPasses = std::any_cast<Hyprlang::INT>(props.at("blur_passes"));
blurSize = std::any_cast<Hyprlang::INT>(props.at("blur_size"));
vibrancy = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy"));
vibrancy_darkness = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy_darkness"));
noise = std::any_cast<Hyprlang::FLOAT>(props.at("noise"));
brightness = std::any_cast<Hyprlang::FLOAT>(props.at("brightness"));
contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast"));
void CBackground::registerSelf(const SP<CBackground>& self) {
m_self = self;
}
void CBackground::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
try {
color = std::any_cast<Hyprlang::INT>(props.at("color"));
blurPasses = std::any_cast<Hyprlang::INT>(props.at("blur_passes"));
blurSize = std::any_cast<Hyprlang::INT>(props.at("blur_size"));
vibrancy = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy"));
vibrancy_darkness = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy_darkness"));
noise = std::any_cast<Hyprlang::FLOAT>(props.at("noise"));
brightness = std::any_cast<Hyprlang::FLOAT>(props.at("brightness"));
contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast"));
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
crossFadeTime = std::any_cast<Hyprlang::FLOAT>(props.at("crossfade_time"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CBackground: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing propperty for CBackground: {}", e.what()); //
}
isScreenshot = path == "screenshot";
viewport = pOutput->getViewport();
outputPort = pOutput->stringPort;
transform = isScreenshot ? wlTransformToHyprutils(invertTransform(pOutput->transform)) : HYPRUTILS_TRANSFORM_NORMAL;
if (isScreenshot) {
resourceID = CScreencopyFrame::getResourceId(pOutput);
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (g_pRenderer->asyncResourceGatherer->gathered) {
if (!g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID))
resourceID = ""; // Fallback to solid color (background:color)
}
if (!g_pHyprlock->getScreencopy()) {
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
resourceID = "";
}
} else if (!path.empty())
resourceID = "background:" + path;
if (!isScreenshot && reloadTime > -1) {
try {
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
plantReloadTimer(); // No reloads for screenshots.
}
}
void CBackground::reset() {
if (reloadTimer) {
reloadTimer->cancel();
reloadTimer.reset();
}
if (fade) {
if (fade->crossFadeTimer) {
fade->crossFadeTimer->cancel();
fade->crossFadeTimer.reset();
}
fade.reset();
}
}
void CBackground::renderRect(CHyprColor color) {
CBox monbox = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderRect(monbox, color, 0);
}
static void onReloadTimer(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG) {
PBG->onReloadTimerUpdate();
PBG->plantReloadTimer();
}
}
static void onCrossFadeTimer(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->onCrossFadeTimerUpdate();
}
static void onAssetCallback(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->startCrossFadeOrUpdateRender();
}
bool CBackground::draw(const SRenderData& data) {
if (resourceID.empty()) {
CBox monbox = {0, 0, viewport.x, viewport.y};
CColor col = color;
CHyprColor col = color;
col.a *= data.opacity;
g_pRenderer->renderRect(monbox, col, 0);
renderRect(col);
return data.opacity < 1.0;
}
if (!asset)
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset)
if (!asset) {
CHyprColor col = color;
col.a *= data.opacity;
renderRect(col);
return true;
}
if (asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
@ -37,11 +134,14 @@ bool CBackground::draw(const SRenderData& data) {
return true;
}
if ((blurPasses > 0 || isScreenshot) && !blurredFB.isAllocated()) {
if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) {
if (firstRender)
firstRender = false;
// make it brah
Vector2D size = asset->texture.m_vSize;
if (output->transform % 2 == 1 && isScreenshot) {
if (transform % 2 == 1 && isScreenshot) {
size.x = asset->texture.m_vSize.y;
size.y = asset->texture.m_vSize.x;
}
@ -59,15 +159,28 @@ bool CBackground::draw(const SRenderData& data) {
else
texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round();
blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit
if (!blurredFB.isAllocated())
blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit
blurredFB.bind();
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0,
isScreenshot ?
wlr_output_transform_invert(output->transform) :
WL_OUTPUT_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs
if (fade)
g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0,
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), 0,
transform);
else
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, transform);
if (blurPasses > 0)
g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness});
g_pRenderer->blurFB(blurredFB,
CRenderer::SBlurParams{.size = blurSize,
.passes = blurPasses,
.noise = noise,
.contrast = contrast,
.brightness = brightness,
.vibrancy = vibrancy,
.vibrancy_darkness = vibrancy_darkness});
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
@ -87,7 +200,116 @@ bool CBackground::draw(const SRenderData& data) {
else
texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round();
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return data.opacity < 1.0;
return fade || data.opacity < 1.0; // actively render during fading
}
void CBackground::plantReloadTimer() {
if (reloadTime == 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
else if (reloadTime > 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
}
void CBackground::onCrossFadeTimerUpdate() {
// Animation done: Unload previous asset, deinitialize the fade and pass the asset
if (fade) {
fade->crossFadeTimer.reset();
fade.reset();
}
if (blurPasses <= 0 && !isScreenshot)
blurredFB.release();
asset = pendingAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
pendingAsset = nullptr;
firstRender = true;
g_pHyprlock->renderOutput(outputPort);
}
void CBackground::onReloadTimerUpdate() {
const std::string OLDPATH = path;
// Path parsing and early returns
if (!reloadCommand.empty()) {
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
if (path.starts_with("file://"))
path = path.substr(7);
if (path.empty())
return;
}
try {
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
if (OLDPATH == path && MTIME == modificationTime)
return;
modificationTime = MTIME;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
return;
// Issue the next request
request.id = std::string{"background:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count());
pendingResourceID = request.id;
request.asset = path;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CBackground::startCrossFadeOrUpdateRender() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
Debug::log(ERR, "New asset had an invalid texture!");
} else if (resourceID != pendingResourceID) {
pendingAsset = newAsset;
if (crossFadeTime > 0) {
// Start a fade
if (!fade)
fade = makeUnique<SFade>(std::chrono::system_clock::now(), 0, nullptr);
else {
// Maybe we where already fading so reset it just in case, but should'nt be happening.
if (fade->crossFadeTimer) {
fade->crossFadeTimer->cancel();
fade->crossFadeTimer.reset();
}
}
fade->start = std::chrono::system_clock::now();
fade->a = 0;
fade->crossFadeTimer =
g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), [REF = m_self](auto, auto) { onCrossFadeTimer(REF); }, nullptr);
} else {
onCrossFadeTimerUpdate();
}
}
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
g_pHyprlock->renderOutput(outputPort);
}

View file

@ -1,37 +1,81 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "../Framebuffer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <hyprutils/math/Misc.hpp>
#include <string>
#include <unordered_map>
#include <any>
#include <chrono>
#include <filesystem>
struct SPreloadedAsset;
class COutput;
struct SFade {
std::chrono::system_clock::time_point start;
float a = 0;
std::shared_ptr<CTimer> crossFadeTimer = nullptr;
};
class CBackground : public IWidget {
public:
CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_);
CBackground() = default;
~CBackground();
void registerSelf(const SP<CBackground>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
private:
// if needed
CFramebuffer blurredFB;
void reset(); // Unload assets, remove timers, etc.
int blurSize = 10;
int blurPasses = 3;
float noise = 0.0117;
float contrast = 0.8916;
float brightness = 0.8172;
float vibrancy = 0.1696;
float vibrancy_darkness = 0.0;
Vector2D viewport;
std::string resourceID;
CColor color;
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
bool isScreenshot = false;
void renderRect(CHyprColor color);
void onReloadTimerUpdate();
void onCrossFadeTimerUpdate();
void plantReloadTimer();
void startCrossFadeOrUpdateRender();
private:
WP<CBackground> m_self;
// if needed
CFramebuffer blurredFB;
int blurSize = 10;
int blurPasses = 3;
float noise = 0.0117;
float contrast = 0.8916;
float brightness = 0.8172;
float vibrancy = 0.1696;
float vibrancy_darkness = 0.0;
Vector2D viewport;
std::string path = "";
std::string outputPort;
Hyprutils::Math::eTransform transform;
std::string resourceID;
std::string pendingResourceID;
float crossFadeTime = -1.0;
CHyprColor color;
SPreloadedAsset* asset = nullptr;
bool isScreenshot = false;
SPreloadedAsset* pendingAsset = nullptr;
bool firstRender = true;
UP<SFade> fade;
int reloadTime = -1;
std::string reloadCommand;
CAsyncResourceGatherer::SPreloadRequest request;
std::shared_ptr<CTimer> reloadTimer;
std::filesystem::file_time_type modificationTime;
};

View file

@ -1,9 +1,11 @@
#include "IWidget.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
#include <chrono>
#include <unistd.h>
#include <pwd.h>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::String;
@ -14,16 +16,24 @@ using namespace Hyprutils::String;
namespace std {
namespace chrono {
using date::current_zone;
using date::locate_zone;
using date::time_zone;
}
}
#endif
static Vector2D rotateVector(const Vector2D& vec, const double& ang) {
const double COS = std::abs(std::cos(ang));
const double SIN = std::abs(std::sin(ang));
return Vector2D((vec.x * COS) + (vec.y * SIN), (vec.x * SIN) + (vec.y * COS));
}
Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang) {
// offset after rotation for alignment
Vector2D rot;
if (ang != 0)
rot = (size - size.rotated(ang)) / 2.0;
rot = (size - rotateVector(size, ang)) / 2.0;
Vector2D pos = offset;
if (halign == "center")
@ -47,19 +57,28 @@ Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size,
return pos;
}
static void replaceAll(std::string& str, const std::string& from, const std::string& to) {
if (from.empty())
return;
size_t pos = 0;
while ((pos = str.find(from, pos)) != std::string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
int IWidget::roundingForBox(const CBox& box, int roundingConfig) {
const int MINHALFBOX = std::min(box.w, box.h) / 2.0;
if (roundingConfig == -1)
return MINHALFBOX;
return std::clamp(roundingConfig, 0, MINHALFBOX);
}
int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness) {
const int MINHALFBORDER = std::min(borderBox.w, borderBox.h) / 2.0;
if (roundingConfig == -1)
return MINHALFBORDER;
else if (roundingConfig == 0)
return 0;
return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER);
}
static void replaceAllAttempts(std::string& str) {
const size_t ATTEMPTS = g_pHyprlock->getPasswordFailedAttempts();
const size_t ATTEMPTS = g_pAuth->getFailedAttempts();
const std::string STR = std::to_string(ATTEMPTS);
size_t pos = 0;
@ -81,35 +100,78 @@ static void replaceAllAttempts(std::string& str) {
}
static void replaceAllLayout(std::string& str) {
std::string layoutName = "error";
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
const auto LAYOUTNAME = xkb_keymap_layout_get_name(g_pHyprlock->m_pXKBKeymap, LAYOUTIDX);
const std::string STR = LAYOUTNAME ? LAYOUTNAME : "error";
size_t pos = 0;
if (g_pSeatManager->m_pXKBKeymap) {
const auto PNAME = xkb_keymap_layout_get_name(g_pSeatManager->m_pXKBKeymap, LAYOUTIDX);
if (PNAME)
layoutName = PNAME;
}
size_t pos = 0;
while ((pos = str.find("$LAYOUT", pos)) != std::string::npos) {
if (str.substr(pos, 8).ends_with('[') && str.substr(pos).contains(']')) {
const std::string REPL = str.substr(pos + 8, str.find_first_of(']', pos) - 8 - pos);
const CVarList LANGS(REPL);
const std::string LANG = LANGS[LAYOUTIDX].empty() ? STR : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
if (LAYOUTIDX >= LANGS.size()) {
Debug::log(ERR, "Layout index {} out of bounds. Max is {}.", LAYOUTIDX, LANGS.size() - 1);
continue;
}
const std::string LANG = LANGS[LAYOUTIDX].empty() ? layoutName : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
str.replace(pos, 9 + REPL.length(), LANG);
pos += LANG.length();
} else {
str.replace(pos, 7, STR);
pos += STR.length();
str.replace(pos, 7, layoutName);
pos += layoutName.length();
}
}
}
static std::string getTime() {
const auto current_zone = std::chrono::current_zone();
const auto HHMMSS = std::chrono::hh_mm_ss{current_zone->to_local(std::chrono::system_clock::now()) -
std::chrono::floor<std::chrono::days>(current_zone->to_local(std::chrono::system_clock::now()))};
const auto HRS = HHMMSS.hours().count();
const auto MINS = HHMMSS.minutes().count();
static bool logMissingTzOnce = true;
static std::chrono::hh_mm_ss<std::chrono::system_clock::duration> getTime() {
const std::chrono::time_zone* pCurrentTz = nullptr;
try {
auto name = std::getenv("TZ");
if (name)
pCurrentTz = std::chrono::locate_zone(name);
} catch (std::runtime_error&) { Debug::log(WARN, "Invalid TZ value. Falling back to current timezone!"); }
if (!pCurrentTz)
pCurrentTz = std::chrono::current_zone();
const auto TPNOW = std::chrono::system_clock::now();
//
std::chrono::hh_mm_ss<std::chrono::system_clock::duration> hhmmss;
if (!pCurrentTz) {
if (logMissingTzOnce) {
Debug::log(WARN, "Current timezone unknown. Falling back to UTC!");
logMissingTzOnce = false;
}
hhmmss = std::chrono::hh_mm_ss{TPNOW - std::chrono::floor<std::chrono::days>(TPNOW)};
} else
hhmmss = std::chrono::hh_mm_ss{pCurrentTz->to_local(TPNOW) - std::chrono::floor<std::chrono::days>(pCurrentTz->to_local(TPNOW))};
return hhmmss;
}
static std::string getTime24h() {
const auto HHMMSS = getTime();
const auto HRS = HHMMSS.hours().count();
const auto MINS = HHMMSS.minutes().count();
return (HRS < 10 ? "0" : "") + std::to_string(HRS) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS);
}
static std::string getTime12h() {
const auto HHMMSS = getTime();
const auto HRS = HHMMSS.hours().count();
const auto MINS = HHMMSS.minutes().count();
return (HRS == 12 || HRS == 0 ? "12" : (HRS % 12 < 10 ? "0" : "") + std::to_string(HRS % 12)) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS) +
(HRS < 12 ? " AM" : " PM");
}
IWidget::SFormatResult IWidget::formatString(std::string in) {
auto uidPassword = getpwuid(getuid());
@ -123,25 +185,18 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
Debug::log(WARN, "Error in formatString, user_gecos null. Errno: ", errno);
IWidget::SFormatResult result;
replaceAll(in, "$DESC", std::string{user_gecos ? user_gecos : ""});
replaceAll(in, "$USER", std::string{username ? username : ""});
replaceAll(in, "<br/>", std::string{"\n"});
replaceInString(in, "$DESC", std::string{user_gecos ? user_gecos : ""});
replaceInString(in, "$USER", std::string{username ? username : ""});
replaceInString(in, "<br/>", std::string{"\n"});
if (in.contains("$TIME")) {
replaceAll(in, "$TIME", getTime());
if (in.contains("$TIME12")) {
replaceInString(in, "$TIME12", getTime12h());
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
}
if (in.contains("$FAIL")) {
const auto FAIL = g_pAuth->getLastFailText();
replaceAll(in, "$FAIL", FAIL.has_value() ? FAIL.value() : "");
result.allowForceUpdate = true;
}
if (in.contains("$PROMPT")) {
const auto PROMPT = g_pAuth->getLastPrompt();
replaceAll(in, "$PROMPT", PROMPT.has_value() ? PROMPT.value() : "");
result.allowForceUpdate = true;
if (in.contains("$TIME")) {
replaceInString(in, "$TIME", getTime24h());
result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000;
}
if (in.contains("$ATTEMPTS")) {
@ -154,6 +209,36 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
result.allowForceUpdate = true;
}
if (in.contains("$FAIL")) {
const auto FAIL = g_pAuth->getCurrentFailText();
replaceInString(in, "$FAIL", FAIL);
result.allowForceUpdate = true;
}
if (in.contains("$PAMFAIL")) {
const auto FAIL = g_pAuth->getFailText(AUTH_IMPL_PAM);
replaceInString(in, "$PAMFAIL", FAIL.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$PAMPROMPT")) {
const auto PROMPT = g_pAuth->getPrompt(AUTH_IMPL_PAM);
replaceInString(in, "$PAMPROMPT", PROMPT.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$FPRINTFAIL")) {
const auto FPRINTFAIL = g_pAuth->getFailText(AUTH_IMPL_FINGERPRINT);
replaceInString(in, "$FPRINTFAIL", FPRINTFAIL.value_or(""));
result.allowForceUpdate = true;
}
if (in.contains("$FPRINTPROMPT")) {
const auto FPRINTPROMPT = g_pAuth->getPrompt(AUTH_IMPL_FINGERPRINT);
replaceInString(in, "$FPRINTPROMPT", FPRINTPROMPT.value_or(""));
result.allowForceUpdate = true;
}
if (in.starts_with("cmd[") && in.contains("]")) {
// this is a command
CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true);
@ -181,3 +266,15 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
result.formatted = in;
return result;
}
void IWidget::setHover(bool hover) {
hovered = hover;
}
bool IWidget::isHovered() const {
return hovered;
}
bool IWidget::containsPoint(const Vector2D& pos) const {
return getBoundingBoxWl().containsPoint(pos);
}

View file

@ -1,19 +1,35 @@
#pragma once
#include "../../helpers/Vector2D.hpp"
#include "../../defines.hpp"
#include "../../helpers/Math.hpp"
#include <string>
#include <unordered_map>
#include <any>
class COutput;
class IWidget {
public:
struct SRenderData {
float opacity = 1;
};
virtual ~IWidget() = default;
virtual 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;
virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
const double& ang = 0);
static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
const double& ang = 0);
static int roundingForBox(const CBox& box, int roundingConfig);
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
virtual CBox getBoundingBoxWl() const {
return CBox();
};
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}
virtual void onHover(const Vector2D& pos) {}
bool containsPoint(const Vector2D& pos) const;
struct SFormatResult {
std::string formatted;
@ -23,5 +39,11 @@ class IWidget {
bool allowForceUpdate = false;
};
virtual SFormatResult formatString(std::string in);
static SFormatResult formatString(std::string in);
void setHover(bool hover);
bool isHovered() const;
private:
bool hovered = false;
};

View file

@ -2,32 +2,37 @@
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
CImage::~CImage() {
if (imageTimer) {
imageTimer->cancel();
imageTimer.reset();
reset();
}
void CImage::registerSelf(const SP<CImage>& self) {
m_self = self;
}
static void onTimer(WP<CImage> ref) {
if (auto PIMAGE = ref.lock(); PIMAGE) {
PIMAGE->onTimerUpdate();
PIMAGE->plantTimer();
}
}
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->onTimerUpdate();
PIMAGE->plantTimer();
}
static void onAssetCallback(void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->renderSuper();
static void onAssetCallback(WP<CImage> ref) {
if (auto PIMAGE = ref.lock(); PIMAGE)
PIMAGE->renderUpdate();
}
void CImage::onTimerUpdate() {
const std::string OLDPATH = path;
if (!reloadCommand.empty()) {
path = g_pHyprlock->spawnSync(reloadCommand);
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
@ -40,7 +45,7 @@ void CImage::onTimerUpdate() {
}
try {
const auto MTIME = std::filesystem::last_write_time(path);
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
if (OLDPATH == path && MTIME == modificationTime)
return;
@ -58,9 +63,7 @@ void CImage::onTimerUpdate() {
pendingResourceID = request.id;
request.asset = path;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
request.callback = onAssetCallback;
request.callbackData = this;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
@ -68,55 +71,72 @@ void CImage::onTimerUpdate() {
void CImage::plantTimer() {
if (reloadTime == 0) {
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, true);
} else if (reloadTime > 0)
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false);
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, false);
}
CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) :
viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) {
void CImage::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
size = std::any_cast<Hyprlang::INT>(props.at("size"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("border_color"));
pos = std::any_cast<Hyprlang::VEC2>(props.at("position"));
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
viewport = pOutput->getViewport();
stringPort = pOutput->stringPort;
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"));
shadow.configure(m_self.lock(), props, viewport);
try {
modificationTime = std::filesystem::last_write_time(path);
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
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"));
angle = angle * M_PI / 180.0;
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()); //
}
plantTimer();
resourceID = "image:" + path;
angle = angle * M_PI / 180.0;
if (reloadTime > -1) {
try {
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
plantTimer();
}
}
void CImage::reset() {
if (imageTimer) {
imageTimer->cancel();
imageTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
imageFB.release();
if (asset && reloadTime > -1) // Don't unload asset if it's a static image
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = nullptr;
pendingResourceID = "";
resourceID = "";
}
bool CImage::draw(const SRenderData& data) {
if (!pendingResourceID.empty()) {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
} else if (resourceID != pendingResourceID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
imageFB.release();
asset = newAsset;
resourceID = pendingResourceID;
firstRender = true;
}
pendingResourceID = "";
}
}
if (resourceID.empty())
return false;
@ -146,14 +166,14 @@ bool CImage::draw(const SRenderData& data) {
texbox.w *= std::max(SCALEX, SCALEY);
texbox.h *= std::max(SCALEX, SCALEY);
const bool ALLOWROUND = rounding > -1 && rounding < std::min(texbox.w, texbox.h) / 2.0;
// plus borders if any
CBox borderBox = {angle == 0 ? BORDERPOS : BORDERPOS + Vector2D{1.0, 1.0}, texbox.size() + IMAGEPOS * 2.0};
borderBox.round();
const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0};
const Vector2D FBSIZE = angle == 0 ? borderBox.size() : borderBox.size() + Vector2D{2.0, 2.0};
const int ROUND = roundingForBox(texbox, rounding);
const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border);
imageFB.alloc(FBSIZE.x, FBSIZE.y, true);
g_pRenderer->pushFb(imageFB.m_iFb);
@ -161,10 +181,10 @@ bool CImage::draw(const SRenderData& data) {
glClear(GL_COLOR_BUFFER_BIT);
if (border > 0)
g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : std::min(borderBox.w, borderBox.h) / 2.0);
g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0);
texbox.round();
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, WL_OUTPUT_TRANSFORM_NORMAL);
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
g_pRenderer->popFb();
}
@ -178,26 +198,60 @@ bool CImage::draw(const SRenderData& data) {
shadow.draw(data);
const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle);
pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle);
texbox.x = TEXPOS.x;
texbox.y = TEXPOS.y;
texbox.x = pos.x;
texbox.y = pos.y;
texbox.round();
texbox.rot = angle;
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return data.opacity < 1.0;
}
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PIMAGE = (CImage*)data;
PIMAGE->renderSuper();
void CImage::renderUpdate() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
} else if (resourceID != pendingResourceID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
imageFB.release();
asset = newAsset;
resourceID = pendingResourceID;
firstRender = true;
}
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
g_pHyprlock->renderOutput(stringPort);
}
void CImage::renderSuper() {
g_pHyprlock->renderOutput(output->stringPort);
CBox CImage::getBoundingBoxWl() const {
if (!imageFB.isAllocated())
return CBox{};
if (!pendingResourceID.empty()) /* did not consume the pending resource */
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
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);
}

View file

@ -1,8 +1,9 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include "Shadowable.hpp"
@ -16,24 +17,35 @@ class COutput;
class CImage : public IWidget {
public:
CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props);
CImage() = default;
~CImage();
virtual bool draw(const SRenderData& data);
void registerSelf(const SP<CImage>& self);
void renderSuper();
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
WP<CImage> m_self;
CFramebuffer imageFB;
int size;
int rounding;
double border;
double angle;
CColor color;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
std::string halign, valign, path;
@ -41,14 +53,17 @@ class CImage : public IWidget {
int reloadTime;
std::string reloadCommand;
std::string onclickCommand;
std::filesystem::file_time_type modificationTime;
std::shared_ptr<CTimer> imageTimer;
CAsyncResourceGatherer::SPreloadRequest request;
Vector2D viewport;
std::string stringPort;
std::string resourceID;
std::string pendingResourceID; // if reloading image
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
SPreloadedAsset* asset = nullptr;
CShadowable shadow;
};

View file

@ -1,30 +1,33 @@
#include "Label.hpp"
#include "../../helpers/Color.hpp"
#include <hyprlang.hpp>
#include "../Renderer.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include <hyprlang.hpp>
#include <stdexcept>
CLabel::~CLabel() {
if (labelTimer) {
labelTimer->cancel();
labelTimer.reset();
reset();
}
void CLabel::registerSelf(const SP<CLabel>& self) {
m_self = self;
}
static void onTimer(WP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL) {
// update label
PLABEL->onTimerUpdate();
// plant new timer
PLABEL->plantTimer();
}
}
static void onTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PLABEL = (CLabel*)data;
// update label
PLABEL->onTimerUpdate();
// plant new timer
PLABEL->plantTimer();
}
static void onAssetCallback(void* data) {
const auto PLABEL = (CLabel*)data;
PLABEL->renderSuper();
static void onAssetCallback(WP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL)
PLABEL->renderUpdate();
}
std::string CLabel::getUniqueResourceId() {
@ -49,79 +52,96 @@ void CLabel::onTimerUpdate() {
pendingResourceID = request.id;
request.asset = label.formatted;
request.callback = onAssetCallback;
request.callbackData = this;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CLabel::plantTimer() {
if (label.updateEveryMs != 0)
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), onTimer, this, label.allowForceUpdate);
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), [REF = m_self](auto, auto) { onTimer(REF); }, this, label.allowForceUpdate);
else if (label.updateEveryMs == 0 && label.allowForceUpdate)
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, this, true);
}
CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
outputStringPort(output), shadow(this, props, viewport_) {
labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text"));
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
CColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
void CLabel::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
label = formatString(labelPreFormat);
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
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;
shadow.configure(m_self.lock(), props, viewport);
if (!textAlign.empty())
request.props["text_align"] = textAlign;
try {
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text"));
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
angle = angle * M_PI / 180.0;
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
CHyprColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
label = formatString(labelPreFormat);
request.id = getUniqueResourceId();
resourceID = request.id;
request.asset = label.formatted;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = labelColor;
request.props["font_size"] = fontSize;
request.props["cmd"] = label.cmd;
if (!textAlign.empty())
request.props["text_align"] = textAlign;
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CLabel: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CLabel: {}", e.what()); //
}
pos = configPos; // Label size not known yet
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
pos = {POS__.x, POS__.y};
configPos = pos;
viewport = viewport_;
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
angle = angle * M_PI / 180.0;
plantTimer();
}
void CLabel::reset() {
if (labelTimer) {
labelTimer->cancel();
labelTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
if (asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = nullptr;
pendingResourceID.clear();
resourceID.clear();
}
bool CLabel::draw(const SRenderData& data) {
if (!asset) {
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset)
return true;
shadow.markShadowDirty();
}
if (!pendingResourceID.empty()) {
// new asset is pending
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
// new asset is ready :D
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = newAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
shadow.markShadowDirty();
}
if (updateShadow) {
updateShadow = false;
shadow.markShadowDirty();
}
shadow.draw(data);
@ -136,11 +156,41 @@ bool CLabel::draw(const SRenderData& data) {
return false;
}
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PLABEL = (CLabel*)data;
PLABEL->renderSuper();
}
void CLabel::renderUpdate() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
// new asset is ready :D
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = newAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
updateShadow = true;
} else {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
return;
}
void CLabel::renderSuper() {
g_pHyprlock->renderOutput(outputStringPort);
}
CBox CLabel::getBoundingBoxWl() const {
if (!asset)
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y},
asset->texture.m_vSize,
};
}
void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CLabel::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -2,7 +2,7 @@
#include "IWidget.hpp"
#include "Shadowable.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <string>
@ -14,16 +14,26 @@ class CSessionLockSurface;
class CLabel : public IWidget {
public:
CLabel(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output);
CLabel() = default;
~CLabel();
virtual bool draw(const SRenderData& data);
void registerSelf(const SP<CLabel>& self);
void renderSuper();
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
WP<CLabel> m_self;
std::string getUniqueResourceId();
std::string labelPreFormat;
@ -36,13 +46,15 @@ class CLabel : public IWidget {
std::string resourceID;
std::string pendingResourceID; // if dynamic label
std::string halign, valign;
std::string onclickCommand;
SPreloadedAsset* asset = nullptr;
std::string outputStringPort;
CAsyncResourceGatherer::SPreloadRequest request;
std::shared_ptr<CTimer> labelTimer;
std::shared_ptr<CTimer> labelTimer = nullptr;
CShadowable shadow;
bool updateShadow = true;
};

View file

@ -1,88 +1,127 @@
#include "PasswordInputField.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "src/core/Auth.hpp"
#include "../../auth/Auth.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../config/ConfigManager.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/AnimationManager.hpp"
#include "../../helpers/Color.hpp"
#include <cmath>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/String.hpp>
#include <algorithm>
#include <hyprlang.hpp>
static void replaceAll(std::string& str, const std::string& from, const std::string& to) {
if (from.empty())
return;
size_t pos = 0;
while ((pos = str.find(from, pos)) != std::string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
using namespace Hyprutils::String;
CPasswordInputField::~CPasswordInputField() {
reset();
}
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
outputStringPort(output), shadow(this, props, viewport_) {
size = std::any_cast<Hyprlang::VEC2>(props.at("size"));
outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
dots.size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size"));
dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding"));
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout"));
hiddenInputState.enabled = std::any_cast<Hyprlang::INT>(props.at("hide_input"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
configPlaceholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
col.transitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition"));
col.outer = std::any_cast<Hyprlang::INT>(props.at("outer_color"));
col.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
col.font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
col.fail = std::any_cast<Hyprlang::INT>(props.at("fail_color"));
col.check = std::any_cast<Hyprlang::INT>(props.at("check_color"));
col.both = std::any_cast<Hyprlang::INT>(props.at("bothlock_color"));
col.caps = std::any_cast<Hyprlang::INT>(props.at("capslock_color"));
col.num = std::any_cast<Hyprlang::INT>(props.at("numlock_color"));
col.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
col.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
viewport = viewport_;
void CPasswordInputField::registerSelf(const SP<CPasswordInputField>& self) {
m_self = self;
}
auto POS__ = std::any_cast<Hyprlang::VEC2>(props.at("position"));
pos = {POS__.x, POS__.y};
configPos = pos;
configSize = size;
void CPasswordInputField::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
pos = posFromHVAlign(viewport, size, pos, halign, valign);
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
dots.spacing = std::clamp(dots.spacing, 0.f, 1.f);
col.transitionMs = std::clamp(col.transitionMs, 0, 1000);
shadow.configure(m_self.lock(), props, viewport);
col.both = col.both == -1 ? col.outer : col.both;
col.caps = col.caps == -1 ? col.outer : col.caps;
col.num = col.num == -1 ? col.outer : col.num;
try {
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
configSize = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
dots.size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size"));
dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding"));
dots.textFormat = std::any_cast<Hyprlang::STRING>(props.at("dots_text_format"));
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout"));
hiddenInputState.enabled = std::any_cast<Hyprlang::INT>(props.at("hide_input"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
configPlaceholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color"));
colorConfig.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
colorConfig.font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
colorConfig.fail = CGradientValueData::fromAnyPv(props.at("fail_color"));
colorConfig.check = CGradientValueData::fromAnyPv(props.at("check_color"));
colorConfig.both = CGradientValueData::fromAnyPv(props.at("bothlock_color"));
colorConfig.caps = CGradientValueData::fromAnyPv(props.at("capslock_color"));
colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color"));
colorConfig.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
colorConfig.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
colorConfig.hiddenBase = std::any_cast<Hyprlang::INT>(props.at("hide_input_base_color"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CPasswordInputField: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CPasswordInputField: {}", e.what()); //
}
g_pHyprlock->m_bNumLock = col.invertNum;
configPos = pos;
colorState.font = colorConfig.font;
// Render placeholder if either placeholder_text or fail_text are non-empty
// as placeholder must be rendered to show fail_text
if (!configPlaceholderText.empty() || !configFailText.empty()) {
placeholder.currentText = configPlaceholderText;
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);
replaceAll(placeholder.currentText, "$PROMPT", "");
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this);
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 = placeholder.resourceID;
request.asset = placeholder.currentText;
request.id = dots.textResourceID;
request.asset = dots.textFormat;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = std::string{"Sans"};
request.props["color"] = CColor{1.0 - col.font.r, 1.0 - col.font.g, 1.0 - col.font.b, 0.5};
request.props["font_size"] = (int)size.y / 4;
request.props["font_family"] = fontFamily;
request.props["color"] = colorConfig.font;
request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
// request the inital placeholder asset
updatePlaceholder();
}
static void fadeOutCallback(std::shared_ptr<CTimer> self, void* data) {
CPasswordInputField* p = (CPasswordInputField*)data;
void CPasswordInputField::reset() {
if (fade.fadeOutTimer.get()) {
fade.fadeOutTimer->cancel();
fade.fadeOutTimer.reset();
}
p->onFadeOutTimer();
if (g_pHyprlock->m_bTerminate)
return;
if (placeholder.asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
placeholder.asset = nullptr;
placeholder.resourceID.clear();
placeholder.currentText.clear();
}
static void fadeOutCallback(WP<CPasswordInputField> ref) {
if (const auto PP = ref.lock(); PP)
PP->onFadeOutTimer();
}
void CPasswordInputField::onFadeOutTimer() {
@ -94,7 +133,7 @@ void CPasswordInputField::onFadeOutTimer() {
void CPasswordInputField::updateFade() {
if (!fadeOnEmpty) {
fade.a = 1.0;
fade.a->setValueAndWarp(1.0);
return;
}
@ -108,67 +147,34 @@ void CPasswordInputField::updateFade() {
fade.fadeOutTimer.reset();
}
if (!INPUTUSED && fade.a != 0.0 && (!fade.animated || fade.appearing)) {
if (!INPUTUSED && fade.a->goal() != 0.0) {
if (fade.allowFadeOut || fadeTimeoutMs == 0) {
fade.a = 1.0;
fade.animated = true;
fade.appearing = false;
fade.start = std::chrono::system_clock::now();
*fade.a = 0.0;
fade.allowFadeOut = false;
} else if (!fade.fadeOutTimer.get())
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), fadeOutCallback, this);
}
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr);
if (INPUTUSED && fade.a != 1.0 && (!fade.animated || !fade.appearing)) {
fade.a = 0.0;
fade.animated = true;
fade.appearing = true;
fade.start = std::chrono::system_clock::now();
}
if (fade.animated) {
if (fade.appearing)
fade.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
else
fade.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0))
fade.animated = false;
} else if (INPUTUSED && fade.a->goal() != 1.0)
*fade.a = 1.0;
if (fade.a->isBeingAnimated())
redrawShadow = true;
}
}
void CPasswordInputField::updateDots() {
if (passwordLength == dots.currentAmount)
if (dots.currentAmount->goal() == passwordLength)
return;
if (std::abs(passwordLength - dots.currentAmount) > 1) {
dots.currentAmount = std::clamp(dots.currentAmount, passwordLength - 1.f, passwordLength + 1.f);
dots.lastFrame = std::chrono::system_clock::now();
}
if (checkWaiting)
return;
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000);
const float TOADD = DELTA / 1000000.0 * dots.speedPerSecond;
if (passwordLength > dots.currentAmount) {
dots.currentAmount += TOADD;
if (dots.currentAmount > passwordLength)
dots.currentAmount = passwordLength;
} else if (passwordLength < dots.currentAmount) {
dots.currentAmount -= TOADD;
if (dots.currentAmount < passwordLength)
dots.currentAmount = passwordLength;
}
dots.lastFrame = std::chrono::system_clock::now();
if (passwordLength == 0)
dots.currentAmount->setValueAndWarp(passwordLength);
else
*dots.currentAmount = passwordLength;
}
bool CPasswordInputField::draw(const SRenderData& data) {
CBox inputFieldBox = {pos, size};
CBox outerBox = {pos - Vector2D{outThick, outThick}, size + Vector2D{outThick * 2, outThick * 2}};
if (firstRender || redrawShadow) {
firstRender = false;
redrawShadow = false;
@ -179,52 +185,38 @@ bool CPasswordInputField::draw(const SRenderData& data) {
passwordLength = g_pHyprlock->getPasswordBufferDisplayLen();
checkWaiting = g_pAuth->checkWaiting();
displayFail = g_pAuth->m_bDisplayFailText;
updateFade();
updateDots();
updatePlaceholder();
updateColors();
updatePlaceholder();
updateWidth();
updateHiddenInputState();
static auto TIMER = std::chrono::system_clock::now();
if (placeholder.asset) {
const auto TARGETSIZEX = placeholder.asset->texture.m_vSize.x + inputFieldBox.h;
if (size.x < TARGETSIZEX) {
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - TIMER).count(), 8000, 20000);
TIMER = std::chrono::system_clock::now();
forceReload = true;
size.x += std::clamp((TARGETSIZEX - size.x) * DELTA / 100000.0, 1.0, 1000.0);
if (size.x > TARGETSIZEX) {
size.x = TARGETSIZEX;
redrawShadow = true;
}
}
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
} else if (size.x != configSize.x) {
size.x = configSize.x;
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
}
CBox inputFieldBox = {pos, size->value()};
CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}};
SRenderData shadowData = data;
shadowData.opacity *= fade.a;
shadow.draw(shadowData);
shadowData.opacity *= fade.a->value();
CColor outerCol = col.outer;
outerCol.a *= fade.a * data.opacity;
CColor innerCol = col.inner;
innerCol.a *= fade.a * data.opacity;
CColor fontCol = col.font;
fontCol.a *= fade.a * data.opacity;
if (!size->isBeingAnimated())
shadow.draw(shadowData);
//CGradientValueData outerGrad = colorState.outer->value();
//for (auto& c : outerGrad.m_vColors)
// c.a *= fade.a->value() * data.opacity;
CHyprColor innerCol = colorState.inner->value();
innerCol.a *= fade.a->value() * data.opacity;
CHyprColor fontCol = colorState.font;
fontCol.a *= fade.a->value() * data.opacity;
if (outThick > 0) {
g_pRenderer->renderRect(outerBox, outerCol, rounding == -1 ? outerBox.h / 2.0 : rounding);
const auto OUTERROUND = roundingForBorderBox(outerBox, rounding, outThick);
g_pRenderer->renderBorder(outerBox, colorState.outer->value(), outThick, OUTERROUND, fade.a->value() * data.opacity);
if (passwordLength != 0 && hiddenInputState.enabled && !fade.animated && data.opacity == 1.0) {
if (passwordLength != 0 && !checkWaiting && hiddenInputState.enabled) {
CBox outerBoxScaled = outerBox;
Vector2D p = outerBox.pos();
outerBoxScaled.translate(-p).scale(0.5).translate(p);
@ -234,57 +226,82 @@ bool CPasswordInputField::draw(const SRenderData& data) {
outerBoxScaled.x += outerBoxScaled.w;
glEnable(GL_SCISSOR_TEST);
glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h);
g_pRenderer->renderRect(outerBox, hiddenInputState.lastColor, rounding == -1 ? outerBox.h / 2.0 : rounding);
g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
}
}
g_pRenderer->renderRect(inputFieldBox, innerCol, rounding == -1 ? inputFieldBox.h / 2.0 : rounding - outThick);
const int ROUND = roundingForBox(inputFieldBox, rounding);
g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND);
if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) {
const int PASS_SIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f;
const int PASS_SPACING = std::floor(PASS_SIZE * dots.spacing);
const int DOT_PAD = (inputFieldBox.h - PASS_SIZE) / 2;
const int DOT_AREA_WIDTH = inputFieldBox.w - DOT_PAD * 2; // avail width for dots
const int MAX_DOTS = std::round(DOT_AREA_WIDTH * 1.0 / (PASS_SIZE + PASS_SPACING)); // max amount of dots that can fit in the area
const int DOT_FLOORED = std::floor(dots.currentAmount);
const float DOT_ALPHA = fontCol.a;
if (!hiddenInputState.enabled) {
const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f;
Vector2D passSize{RECTPASSSIZE, RECTPASSSIZE};
int passSpacing = std::floor(passSize.x * dots.spacing);
if (!dots.textFormat.empty()) {
if (!dots.textAsset)
dots.textAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(dots.textResourceID);
if (!dots.textAsset)
forceReload = true;
else {
passSize = dots.textAsset->texture.m_vSize;
passSpacing = std::floor(passSize.x * dots.spacing);
}
}
const auto CURRDOTS = dots.currentAmount->value();
const double DOTPAD = (inputFieldBox.h - passSize.y) / 2.0;
const double DOTAREAWIDTH = inputFieldBox.w - (DOTPAD * 2);
const int MAXDOTS = std::round(DOTAREAWIDTH * 1.0 / (passSize.x + passSpacing));
const int DOTFLOORED = std::floor(CURRDOTS);
const auto DOTALPHA = fontCol.a;
// Calculate the total width required for all dots including spaces between them
const int TOTAL_DOTS_WIDTH = (PASS_SIZE + PASS_SPACING) * dots.currentAmount - PASS_SPACING;
const double CURRWIDTH = ((passSize.x + passSpacing) * CURRDOTS) - passSpacing;
// Calculate starting x-position to ensure dots stay centered within the input field
int xstart = dots.center ? (DOT_AREA_WIDTH - TOTAL_DOTS_WIDTH) / 2 + DOT_PAD : DOT_PAD;
double xstart = dots.center ? ((DOTAREAWIDTH - CURRWIDTH) / 2.0) + DOTPAD : DOTPAD;
if (dots.currentAmount > MAX_DOTS)
xstart = (inputFieldBox.w + MAX_DOTS * (PASS_SIZE + PASS_SPACING) - PASS_SPACING - 2 * TOTAL_DOTS_WIDTH) / 2;
if (CURRDOTS > MAXDOTS)
xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2.0;
if (dots.rounding == -1)
dots.rounding = PASS_SIZE / 2.0;
dots.rounding = passSize.x / 2.0;
else if (dots.rounding == -2)
dots.rounding = rounding == -1 ? PASS_SIZE / 2.0 : rounding * dots.size;
dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size;
for (int i = 0; i < dots.currentAmount; ++i) {
if (i < DOT_FLOORED - MAX_DOTS)
for (int i = 0; i < CURRDOTS; ++i) {
if (i < DOTFLOORED - MAXDOTS)
continue;
if (dots.currentAmount != DOT_FLOORED) {
if (i == DOT_FLOORED)
fontCol.a *= (dots.currentAmount - DOT_FLOORED) * data.opacity;
else if (i == DOT_FLOORED - MAX_DOTS)
fontCol.a *= (1 - dots.currentAmount + DOT_FLOORED) * data.opacity;
if (CURRDOTS != DOTFLOORED) {
if (i == DOTFLOORED)
fontCol.a *= (CURRDOTS - DOTFLOORED) * data.opacity;
else if (i == DOTFLOORED - MAXDOTS)
fontCol.a *= (1 - CURRDOTS + DOTFLOORED) * data.opacity;
}
Vector2D dotPosition =
inputFieldBox.pos() + Vector2D{xstart + (int)inputFieldBox.w % 2 / 2.f + i * (PASS_SIZE + PASS_SPACING), inputFieldBox.h / 2.f - PASS_SIZE / 2.f};
CBox box{dotPosition, Vector2D{PASS_SIZE, PASS_SIZE}};
g_pRenderer->renderRect(box, fontCol, dots.rounding);
fontCol.a = DOT_ALPHA;
Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart + (i * (passSize.x + passSpacing)), (inputFieldBox.h / 2.0) - (passSize.y / 2.0)};
CBox box{dotPosition, passSize};
if (!dots.textFormat.empty()) {
if (!dots.textAsset) {
forceReload = true;
fontCol.a = DOTALPHA;
break;
}
g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding);
} else
g_pRenderer->renderRect(box, fontCol, dots.rounding);
fontCol.a = DOTALPHA;
}
}
if (passwordLength == 0 && !placeholder.resourceID.empty()) {
if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) {
SPreloadedAsset* currAsset = nullptr;
if (!placeholder.asset)
@ -293,20 +310,25 @@ bool CPasswordInputField::draw(const SRenderData& data) {
currAsset = placeholder.asset;
if (currAsset) {
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
pos = pos - currAsset->texture.m_vSize / 2.f;
CBox textbox{pos, currAsset->texture.m_vSize};
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->texture.m_vSize / 2.0;
const CBox ASSETBOX{ASSETPOS, currAsset->texture.m_vSize};
// Cut the texture to the width of the input field
glEnable(GL_SCISSOR_TEST);
glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h);
g_pRenderer->renderTexture(ASSETBOX, currAsset->texture, data.opacity * fade.a->value(), 0);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
} else
forceReload = true;
}
return dots.currentAmount != passwordLength || fade.animated || col.animated || redrawShadow || data.opacity < 1.0 || forceReload;
return redrawShadow || forceReload;
}
void CPasswordInputField::updatePlaceholder() {
if (passwordLength != 0) {
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ placeholder.isFailText) {
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) {
std::erase(placeholder.registeredResourceIDs, placeholder.resourceID);
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
placeholder.asset = nullptr;
@ -316,30 +338,34 @@ void CPasswordInputField::updatePlaceholder() {
return;
}
const auto AUTHFEEDBACK = g_pAuth->m_bDisplayFailText ? g_pAuth->getLastFailText().value_or("Ups, no fail text?") : g_pAuth->getLastPrompt().value_or("Ups, no prompt?");
if (placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts)
// already requested a placeholder for the current fail
if (displayFail && placeholder.failedAttempts == g_pAuth->getFailedAttempts())
return;
placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts();
placeholder.isFailText = g_pAuth->m_bDisplayFailText;
placeholder.lastAuthFeedback = AUTHFEEDBACK;
placeholder.failedAttempts = g_pAuth->getFailedAttempts();
placeholder.asset = nullptr;
std::string newText = (displayFail) ? formatString(configFailText).formatted : formatString(configPlaceholderText).formatted;
if (placeholder.isFailText) {
placeholder.currentText = configFailText;
replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK);
replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(placeholder.failedAttempts));
} else {
placeholder.currentText = configPlaceholderText;
replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK);
}
placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this);
if (std::find(placeholder.registeredResourceIDs.begin(), placeholder.registeredResourceIDs.end(), placeholder.resourceID) != placeholder.registeredResourceIDs.end())
// if the text is unchanged we don't need to do anything, unless we are swapping font color
const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont;
if (!ALLOWCOLORSWAP && newText == placeholder.currentText)
return;
const auto NEWRESOURCEID =
std::format("placeholder:{}{}{}{}{}{}", placeholder.currentText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a);
if (placeholder.resourceID == NEWRESOURCEID)
return;
Debug::log(TRACE, "Updating placeholder text: {}", newText);
placeholder.currentText = newText;
placeholder.asset = nullptr;
placeholder.resourceID = NEWRESOURCEID;
if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end())
return;
Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID);
placeholder.registeredResourceIDs.push_back(placeholder.resourceID);
// query
@ -347,12 +373,38 @@ void CPasswordInputField::updatePlaceholder() {
request.id = placeholder.resourceID;
request.asset = placeholder.currentText;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = std::string{"Sans"};
request.props["color"] = (placeholder.isFailText) ? col.fail : col.font;
request.props["font_size"] = (int)size.y / 4;
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)size->value().y / 4;
request.callback = [REF = m_self] {
if (const auto SELF = REF.lock(); SELF)
g_pHyprlock->renderOutput(SELF->outputStringPort);
};
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CPasswordInputField::updateWidth() {
double targetSizeX = configSize.x;
if (passwordLength == 0 && placeholder.asset)
targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y;
targetSizeX = std::max(targetSizeX, configSize.x);
if (size->goal().x != targetSizeX) {
*size = Vector2D{targetSizeX, configSize.y};
size->setCallbackOnEnd([this](auto) {
redrawShadow = true;
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
});
}
if (size->isBeingAnimated()) {
redrawShadow = true;
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
}
}
void CPasswordInputField::updateHiddenInputState() {
if (!hiddenInputState.enabled || (size_t)hiddenInputState.lastPasswordLength == passwordLength)
return;
@ -360,146 +412,75 @@ void CPasswordInputField::updateHiddenInputState() {
// randomize new thang
hiddenInputState.lastPasswordLength = passwordLength;
srand(std::chrono::system_clock::now().time_since_epoch().count());
float r1 = (rand() % 100) / 255.0;
float r2 = (rand() % 100) / 255.0;
int r3 = rand() % 3;
int r4 = rand() % 2;
int r5 = rand() % 2;
const auto BASEOK = colorConfig.hiddenBase.asOkLab();
((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0;
((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0;
// convert to polar coordinates
const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2));
for (int i = 0; i < 3; ++i) {
if (i != r3 && i != ((r3 + r4) % 3)) {
((float*)&hiddenInputState.lastColor.r)[i] = 1.0 - ((float*)&hiddenInputState.lastColor.r)[r5 ? r3 : ((r3 + r4) % 3)];
}
}
// now randomly rotate the hue
const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4;
hiddenInputState.lastColor.a = 1.0;
// convert back to OkLab
const Hyprgraphics::CColor newColor = Hyprgraphics::CColor::SOkLab{
.l = BASEOK.l,
.a = OKICHCHROMA * std::cos(OKICHHUE),
.b = OKICHCHROMA * std::sin(OKICHHUE),
};
hiddenInputState.lastColor = {newColor, 1.0};
hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4;
}
static void changeChannel(const float& source, const float& target, float& subject, const double& multi, bool& animated) {
const float DELTA = target - source;
if (subject != target) {
subject += DELTA * multi;
animated = true;
if ((source < target && subject > target) || (source > target && subject < target))
subject = target;
}
}
static void changeColor(const CColor& source, const CColor& target, CColor& subject, const double& multi, bool& animated) {
changeChannel(source.r, target.r, subject.r, multi, animated);
changeChannel(source.g, target.g, subject.g, multi, animated);
changeChannel(source.b, target.b, subject.b, multi, animated);
changeChannel(source.a, target.a, subject.a, multi, animated);
}
void CPasswordInputField::updateColors() {
static auto OUTER = col.outer, TARGET = OUTER, SOURCE = OUTER;
static auto INNER = col.inner, ITARGET = INNER, ISOURCE = INNER;
static auto FONT = col.font, FTARGET = FONT, FSOURCE = FONT;
const bool BORDERLESS = outThick == 0;
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
const bool BORDERLESS = outThick == 0;
CGradientValueData* targetGrad = nullptr;
if (col.animated) {
// some cases when events happen too quick (within transitionMs)
// TODO: find more?
const bool LOCKCHANGED = col.stateNum != (col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock) || col.stateCaps != g_pHyprlock->m_bCapsLock;
const bool ANIMONCHECK = checkWaiting && (TARGET == (BORDERLESS ? INNER : OUTER) || TARGET == col.fail);
if (g_pHyprlock->m_bCapsLock && NUMLOCK && !colorConfig.both->m_bIsFallback)
targetGrad = colorConfig.both;
else if (g_pHyprlock->m_bCapsLock)
targetGrad = colorConfig.caps;
else if (NUMLOCK && !colorConfig.num->m_bIsFallback)
targetGrad = colorConfig.num;
if (LOCKCHANGED || ANIMONCHECK) {
const bool EQUALCOLORS = ANIMONCHECK && OUTER == col.check;
// to avoid throttle when check_color set to the same as outer.
SOURCE = BORDERLESS ? (EQUALCOLORS ? INNER : col.inner) : col.outer;
FSOURCE = EQUALCOLORS ? FONT : col.font;
ISOURCE = EQUALCOLORS ? INNER : col.inner;
}
} else {
SOURCE = BORDERLESS ? col.inner : col.outer;
FSOURCE = col.font;
ISOURCE = col.inner;
}
if (checkWaiting)
targetGrad = colorConfig.check;
else if (displayFail && passwordLength == 0)
targetGrad = colorConfig.fail;
col.stateNum = col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
col.stateCaps = g_pHyprlock->m_bCapsLock;
CGradientValueData* outerTarget = colorConfig.outer;
CHyprColor innerTarget = colorConfig.inner;
CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
if (!placeholder.isFailText || passwordLength > 0 || (passwordLength == 0 && checkWaiting)) {
if (g_pHyprlock->m_bFadeStarted) {
if (TARGET == col.check)
SOURCE = BORDERLESS ? col.inner : col.outer;
col.transitionMs = 100;
TARGET = BORDERLESS ? INNER : OUTER;
} else if (checkWaiting) {
FTARGET = col.swapFont ? INNER : FONT;
const float PASSALPHA = FTARGET.a * 0.5;
FTARGET.a = PASSALPHA;
TARGET = col.check;
ITARGET = col.swapFont ? FONT : INNER;
} else if (col.stateCaps && col.stateNum && col.both != OUTER) {
TARGET = col.both;
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
} else if (col.stateCaps && col.caps != OUTER) {
TARGET = col.caps;
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
} else if (col.stateNum && col.num != OUTER) {
TARGET = col.num;
FTARGET = col.swapFont && BORDERLESS ? INNER : FONT;
} else {
// if quickly pressed after failure
if (col.animated && TARGET == col.fail)
SOURCE = BORDERLESS ? col.inner : col.outer;
TARGET = BORDERLESS ? INNER : OUTER;
FTARGET = FONT;
ITARGET = INNER;
}
} else {
FSOURCE = col.swapFont ? INNER : FONT;
const float PASSALPHA = FSOURCE.a * 0.5;
FSOURCE.a = PASSALPHA;
FTARGET = FONT;
SOURCE = col.check;
TARGET = col.fail;
ISOURCE = FONT;
ITARGET = FONT;
if (fade.animated || fade.a < 1.0) {
TARGET = BORDERLESS ? INNER : OUTER;
SOURCE = col.fail;
if (targetGrad) {
if (BORDERLESS && colorConfig.swapFont) {
fontTarget = targetGrad->m_vColors.front();
} else if (BORDERLESS && !colorConfig.swapFont) {
innerTarget = targetGrad->m_vColors.front();
// When changing the inner color, the font cannot be fail_color
fontTarget = colorConfig.font;
} else if (targetGrad) {
outerTarget = targetGrad;
}
}
col.animated = false;
if (!BORDERLESS && *outerTarget != colorState.outer->goal())
*colorState.outer = *outerTarget;
const bool SWAPDONE = !BORDERLESS && col.swapFont ? col.inner == ITARGET : true;
if (innerTarget != colorState.inner->goal())
*colorState.inner = innerTarget;
if ((BORDERLESS ? col.inner : col.outer) == TARGET && col.font == FTARGET && SWAPDONE) {
col.shouldStart = true;
return;
}
if (col.shouldStart) {
col.lastFrame = std::chrono::system_clock::now();
col.shouldStart = false;
}
const auto MULTI = col.transitionMs == 0 ?
1.0 :
std::clamp(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - col.lastFrame).count() / (double)col.transitionMs, 0.016, 0.5);
changeColor(SOURCE, TARGET, (BORDERLESS ? col.inner : col.outer), MULTI, col.animated);
changeColor(FSOURCE, FTARGET, col.font, MULTI, col.animated);
if (col.swapFont && !BORDERLESS)
changeColor(ISOURCE, ITARGET, col.inner, MULTI, col.animated);
col.lastFrame = std::chrono::system_clock::now();
colorState.font = fontTarget;
}
CBox CPasswordInputField::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size->value().y},
size->value(),
};
}
void CPasswordInputField::onHover(const Vector2D& pos) {
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
}

View file

@ -1,11 +1,13 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "Shadowable.hpp"
#include <chrono>
#include "../../config/ConfigDataValues.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <vector>
#include <any>
#include <unordered_map>
@ -14,51 +16,64 @@ struct SPreloadedAsset;
class CPasswordInputField : public IWidget {
public:
CPasswordInputField(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props, const std::string& output);
CPasswordInputField() = default;
virtual ~CPasswordInputField();
void registerSelf(const SP<CPasswordInputField>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onHover(const Vector2D& pos);
virtual CBox getBoundingBoxWl() const;
void reset();
void onFadeOutTimer();
private:
void updateDots();
void updateFade();
void updatePlaceholder();
void updateHiddenInputState();
void updateColors();
WP<CPasswordInputField> m_self;
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = 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;
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 {
float currentAmount = 0;
float speedPerSecond = 5; // actually per... something. I am unsure xD
std::chrono::system_clock::time_point lastFrame;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
PHLANIMVAR<float> currentAmount;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
std::string textFormat = "";
std::string textResourceID;
SPreloadedAsset* textAsset = nullptr;
} dots;
struct {
std::chrono::system_clock::time_point start;
float a = 0;
bool appearing = true;
bool animated = false;
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
bool allowFadeOut = false;
PHLANIMVAR<float> a;
bool appearing = true;
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
bool allowFadeOut = false;
} fade;
struct {
@ -67,43 +82,41 @@ class CPasswordInputField : public IWidget {
std::string currentText = "";
size_t failedAttempts = 0;
bool canGetNewText = true;
bool isFailText = false;
std::string lastAuthFeedback;
std::vector<std::string> registeredResourceIDs;
} placeholder;
struct {
CColor lastColor;
int lastQuadrant = 0;
int lastPasswordLength = 0;
bool enabled = false;
CHyprColor lastColor;
int lastQuadrant = 0;
int lastPasswordLength = 0;
bool enabled = false;
} hiddenInputState;
struct {
CColor outer;
CColor inner;
CColor font;
CColor fail;
CColor check;
CColor caps;
CColor num;
CColor both;
CGradientValueData* outer = nullptr;
CHyprColor inner;
CHyprColor font;
CGradientValueData* fail = nullptr;
CGradientValueData* check = nullptr;
CGradientValueData* caps = nullptr;
CGradientValueData* num = nullptr;
CGradientValueData* both = nullptr;
int transitionMs = 0;
bool invertNum = false;
bool animated = false;
bool stateNum = false;
bool stateCaps = false;
bool swapFont = false;
bool shouldStart;
CHyprColor hiddenBase;
//
std::chrono::system_clock::time_point lastFrame;
} col;
int transitionMs = 0;
bool invertNum = false;
bool swapFont = false;
} colorConfig;
struct {
PHLANIMVAR<CGradientValueData> outer;
PHLANIMVAR<CHyprColor> inner;
// Font color is only chaned, when `swap_font_color` is set to true and no border is present.
// It is not animated, because that does not look good and we would need to rerender the text for each frame.
CHyprColor font;
} colorState;
bool fadeOnEmpty;
uint64_t fadeTimeoutMs;

View file

@ -1,7 +1,11 @@
#include "Shadowable.hpp"
#include "../Renderer.hpp"
#include <hyprlang.hpp>
void CShadowable::configure(WP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) {
m_widget = widget_;
viewport = viewport_;
CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_) : widget(widget_), viewport(viewport_) {
size = std::any_cast<Hyprlang::INT>(props.at("shadow_size"));
passes = std::any_cast<Hyprlang::INT>(props.at("shadow_passes"));
color = std::any_cast<Hyprlang::INT>(props.at("shadow_color"));
@ -9,6 +13,10 @@ CShadowable::CShadowable(IWidget* widget_, const std::unordered_map<std::string,
}
void CShadowable::markShadowDirty() {
const auto WIDGET = m_widget.lock();
if (!m_widget)
return;
if (passes == 0)
return;
@ -21,7 +29,7 @@ void CShadowable::markShadowDirty() {
glClear(GL_COLOR_BUFFER_BIT);
ignoreDraw = true;
widget->draw(IWidget::SRenderData{.opacity = 1.0});
WIDGET->draw(IWidget::SRenderData{.opacity = 1.0});
ignoreDraw = false;
g_pRenderer->blurFB(shadowFB, CRenderer::SBlurParams{.size = size, .passes = passes, .colorize = color, .boostA = boostA});
@ -30,13 +38,13 @@ void CShadowable::markShadowDirty() {
}
bool CShadowable::draw(const IWidget::SRenderData& data) {
if (passes == 0)
if (!m_widget || passes == 0)
return true;
if (!shadowFB.isAllocated() || ignoreDraw)
return true;
CBox box = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, WL_OUTPUT_TRANSFORM_NORMAL);
g_pRenderer->renderTexture(box, shadowFB.m_cTex, data.opacity, 0, HYPRUTILS_TRANSFORM_NORMAL);
return true;
}

View file

@ -1,8 +1,8 @@
#pragma once
#include "../Framebuffer.hpp"
#include "../Texture.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "IWidget.hpp"
#include <string>
@ -11,19 +11,21 @@
class CShadowable {
public:
CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
virtual ~CShadowable() = default;
CShadowable() = default;
void configure(WP<IWidget> widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
// instantly re-renders the shadow using the widget's draw() method
void markShadowDirty();
virtual bool draw(const IWidget::SRenderData& data);
private:
IWidget* widget = nullptr;
int size = 10;
int passes = 4;
float boostA = 1.0;
CColor color{0, 0, 0, 1.0};
Vector2D viewport;
WP<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;

View file

@ -1,22 +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 SP<CShape>& self) {
m_self = self;
}
size = std::any_cast<Hyprlang::VEC2>(props.at("size"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("color"));
borderColor = std::any_cast<Hyprlang::INT>(props.at("border_color"));
pos = std::any_cast<Hyprlang::VEC2>(props.at("position"));
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
void CShape::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
viewport = pOutput->getViewport();
viewport = viewport_;
angle = angle * M_PI / 180.0;
shadow.configure(m_self.lock(), props, viewport);
try {
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("color"));
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CShape: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CShape: {}", e.what()); //
}
angle = angle * M_PI / 180.0;
const Vector2D VBORDER = {border, border};
const Vector2D REALSIZE = size + VBORDER * 2.0;
@ -46,10 +64,8 @@ bool CShape::draw(const SRenderData& data) {
if (xray) {
if (border > 0) {
const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI));
CColor borderCol = borderColor;
borderCol.a *= data.opacity;
g_pRenderer->renderRect(borderBox, borderCol, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND));
const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI));
g_pRenderer->renderBorder(borderBox, borderGrad, border, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND), data.opacity);
}
glEnable(GL_SCISSOR_TEST);
@ -62,18 +78,19 @@ bool CShape::draw(const SRenderData& data) {
}
if (!shapeFB.isAllocated()) {
const auto MINHALFSHAPE = std::min(shapeBox.w, shapeBox.h) / 2.0;
const bool ALLOWROUND = rounding > -1 && rounding < MINHALFSHAPE;
const int ROUND = roundingForBox(shapeBox, rounding);
const int BORDERROUND = roundingForBorderBox(borderBox, rounding, border);
Debug::log(LOG, "round: {}, borderround: {}", ROUND, BORDERROUND);
shapeFB.alloc(borderBox.width + borderBox.x * 2.0, borderBox.height + borderBox.y * 2.0, true);
shapeFB.alloc(borderBox.width + (borderBox.x * 2.0), borderBox.height + (borderBox.y * 2.0), true);
g_pRenderer->pushFb(shapeFB.m_iFb);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (border > 0)
g_pRenderer->renderRect(borderBox, borderColor, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER);
g_pRenderer->renderBorder(borderBox, borderGrad, border, BORDERROUND, 1.0);
g_pRenderer->renderRect(shapeBox, color, ALLOWROUND ? rounding : MINHALFSHAPE);
g_pRenderer->renderRect(shapeBox, color, ROUND);
g_pRenderer->popFb();
}
@ -83,7 +100,23 @@ bool CShape::draw(const SRenderData& data) {
texbox.round();
texbox.rot = angle;
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, WL_OUTPUT_TRANSFORM_FLIPPED_180);
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return data.opacity < 1.0;
}
CBox CShape::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size.y},
size,
};
}
void CShape::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CShape::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -1,38 +1,48 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Box.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "Shadowable.hpp"
#include <hyprutils/math/Box.hpp>
#include <string>
#include <unordered_map>
#include <any>
class CShape : public IWidget {
public:
CShape(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props);
CShape() = default;
virtual ~CShape() = default;
void registerSelf(const SP<CShape>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
private:
CFramebuffer shapeFB;
WP<CShape> m_self;
int rounding;
double border;
double angle;
CColor color;
CColor borderColor;
Vector2D size;
Vector2D pos;
CBox shapeBox;
CBox borderBox;
bool xray;
CFramebuffer shapeFB;
std::string halign, valign;
int rounding;
double border;
double angle;
CHyprColor color;
CGradientValueData borderGrad;
Vector2D size;
Vector2D pos;
CBox shapeBox;
CBox borderBox;
bool xray;
bool firstRender = true;
std::string halign, valign;
Vector2D viewport;
CShadowable shadow;
bool firstRender = true;
std::string onclickCommand;
Vector2D viewport;
CShadowable shadow;
};